Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1c266ae

Browse files
committed
新增发表文章功能,集成 ckeditor
1 parent 882346b commit 1c266ae

37 files changed

+2028
-35
lines changed

config/env.sample.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ env = dev
33

44
log_level = DEBUG
55
domain = 127.0.0.1:8088
6+
website_name = Go语言中文网
67
cookie_secret = 9HFEp6b2DMn^fRduZc
78
data_path = data/max_online_num
89

src/http/controller/article.go

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
package controller
88

99
import (
10+
"http/middleware"
1011
"logic"
1112
"net/http"
1213
"strings"
1314

1415
"github.com/labstack/echo"
16+
"github.com/polaris1119/echoutils"
1517
"github.com/polaris1119/goutils"
1618
"github.com/polaris1119/logger"
1719

@@ -29,11 +31,14 @@ func init() {
2931
type ArticleController struct{}
3032

3133
// 注册路由
32-
func (this *ArticleController) RegisterRoute(g *echo.Group) {
33-
g.Get("/articles", this.ReadList)
34-
g.Get("/articles/crawl", this.Crawl)
34+
func (self ArticleController) RegisterRoute(g *echo.Group) {
35+
g.Get("/articles", self.ReadList)
36+
g.Get("/articles/crawl", self.Crawl)
3537

36-
g.Get("/articles/:id", this.Detail)
38+
g.Get("/articles/:id", self.Detail)
39+
40+
g.Match([]string{"GET", "POST"}, "/articles/new", self.Create, middleware.NeedLogin(), middleware.Sensivite(), middleware.PublishNotice())
41+
g.Post("/articles/modify", self.Modify, middleware.NeedLogin(), middleware.Sensivite())
3742
}
3843

3944
// ReadList 网友文章列表页
@@ -131,6 +136,49 @@ func (ArticleController) Detail(ctx echo.Context) error {
131136
return render(ctx, "articles/detail.html,common/comment.html", map[string]interface{}{"activeArticles": "active", "article": article, "prev": prevNext[0], "next": prevNext[1], "likeflag": likeFlag, "hadcollect": hadCollect})
132137
}
133138

139+
// Create 发布新文章
140+
func (ArticleController) Create(ctx echo.Context) error {
141+
title := ctx.FormValue("title")
142+
if title == "" || ctx.Request().Method() != "POST" {
143+
return render(ctx, "articles/new.html", map[string]interface{}{"activeArticles": "active"})
144+
}
145+
146+
if ctx.FormValue("content") == "" || ctx.FormValue("txt") == "" {
147+
return fail(ctx, 1, "内容不能为空")
148+
}
149+
150+
me := ctx.Get("user").(*model.Me)
151+
err := logic.DefaultArticle.Publish(echoutils.WrapEchoContext(ctx), me, ctx.FormParams())
152+
if err != nil {
153+
return fail(ctx, 2, "内部服务错误")
154+
}
155+
156+
return success(ctx, nil)
157+
}
158+
159+
// Modify 修改文章
160+
func (ArticleController) Modify(ctx echo.Context) error {
161+
if ctx.FormValue("id") == "" || ctx.FormValue("content") == "" {
162+
return fail(ctx, 1, "内容不能为空")
163+
}
164+
article, err := logic.DefaultArticle.FindById(ctx, ctx.FormValue("id"))
165+
if err != nil {
166+
return fail(ctx, 2, "文章不存在")
167+
}
168+
169+
me := ctx.Get("user").(*model.Me)
170+
if article.Author != me.Username && !me.IsRoot {
171+
return fail(ctx, 3, "没有修改权限")
172+
}
173+
174+
errMsg, err := logic.DefaultArticle.Modify(echoutils.WrapEchoContext(ctx), me, ctx.FormParams())
175+
if err != nil {
176+
return fail(ctx, 4, errMsg)
177+
}
178+
179+
return success(ctx, nil)
180+
}
181+
134182
func (ArticleController) Crawl(ctx echo.Context) error {
135183
strUrl := ctx.QueryParam("url")
136184

src/http/controller/image.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
package controller
88

99
import (
10+
"encoding/json"
1011
"global"
1112
"io/ioutil"
1213
"logic"
14+
"net/http"
1315
"os"
1416
"path/filepath"
1517

@@ -25,9 +27,73 @@ type ImageController struct{}
2527

2628
func (self ImageController) RegisterRoute(g *echo.Group) {
2729
g.POST("/image/upload", self.Upload)
30+
g.POST("/image/quick_upload", self.QuickUpload)
2831
g.Match([]string{"GET", "POST"}, "/image/transfer", self.Transfer)
2932
}
3033

34+
// QuickUpload CKEditor 编辑器,上传图片,支持粘贴方式上传
35+
func (self ImageController) QuickUpload(ctx echo.Context) error {
36+
objLogger := getLogger(ctx)
37+
38+
file, fileHeader, err := Request(ctx).FormFile("upload")
39+
if err != nil {
40+
objLogger.Errorln("upload error:", err)
41+
return self.quickUploadFail(ctx, err.Error())
42+
}
43+
defer file.Close()
44+
45+
// 如果是临时文件,存在硬盘中,则是 *os.File(大于32M),直接报错
46+
if _, ok := file.(*os.File); ok {
47+
objLogger.Errorln("upload error:file too large!")
48+
return self.quickUploadFail(ctx, "文件太大!")
49+
}
50+
51+
buf, err := ioutil.ReadAll(file)
52+
if err != nil {
53+
return self.quickUploadFail(ctx, "文件读取失败!")
54+
}
55+
if len(buf) > logic.MaxImageSize {
56+
return self.quickUploadFail(ctx, "文件太大!")
57+
}
58+
59+
fileName := goutils.Md5Buf(buf) + filepath.Ext(fileHeader.Filename)
60+
imgDir := times.Format("ymd")
61+
path, err := logic.DefaultUploader.UploadImage(ctx, file, imgDir, buf, filepath.Ext(fileHeader.Filename))
62+
if err != nil {
63+
return self.quickUploadFail(ctx, "文件上传失败!")
64+
}
65+
66+
cdnDomain := global.App.CDNHttp
67+
if goutils.MustBool(ctx.Request().Header().Get("X-Https")) {
68+
cdnDomain = global.App.CDNHttps
69+
}
70+
71+
data := map[string]interface{}{
72+
"uploaded": 1,
73+
"fileName": fileName,
74+
"url": cdnDomain + path,
75+
}
76+
b, err := json.Marshal(data)
77+
if err != nil {
78+
return err
79+
}
80+
return ctx.JSONBlob(http.StatusOK, b)
81+
}
82+
83+
func (ImageController) quickUploadFail(ctx echo.Context, message string) error {
84+
data := map[string]interface{}{
85+
"uploaded": 0,
86+
"error": map[string]string{
87+
"message": message,
88+
},
89+
}
90+
b, err := json.Marshal(data)
91+
if err != nil {
92+
return err
93+
}
94+
return ctx.JSONBlob(http.StatusOK, b)
95+
}
96+
3197
// Upload 上传图片
3298
func (ImageController) Upload(ctx echo.Context) error {
3399
objLogger := getLogger(ctx)

src/logic/article.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"time"
1818

1919
"github.com/PuerkitoBio/goquery"
20+
"github.com/polaris1119/config"
2021
"github.com/polaris1119/logger"
2122
"github.com/polaris1119/times"
2223
"golang.org/x/net/context"
@@ -212,6 +213,46 @@ func (self ArticleLogic) ParseArticle(ctx context.Context, articleUrl string, au
212213
return article, nil
213214
}
214215

216+
func (self ArticleLogic) Publish(ctx context.Context, me *model.Me, form url.Values) error {
217+
objLog := GetLogger(ctx)
218+
219+
article := &model.Article{
220+
Domain: config.ConfigFile.MustValue("global", "domain", "studygolang.com"),
221+
Name: config.ConfigFile.MustValue("global", "website_name", "Go语言中文网"),
222+
Author: me.Username,
223+
AuthorTxt: me.Username,
224+
Title: form.Get("title"),
225+
Content: form.Get("content"),
226+
Txt: form.Get("txt"),
227+
PubDate: times.Format("Y-m-d H:i:s"),
228+
}
229+
230+
requestIdInter := ctx.Value("request_id")
231+
if requestIdInter != nil {
232+
if requestId, ok := requestIdInter.(string); ok {
233+
article.Url = requestId
234+
}
235+
}
236+
if article.Url == "" {
237+
objLog.Errorln("request_id is empty!")
238+
// 理论上不会执行
239+
return errors.New("request_id is empty!")
240+
}
241+
242+
_, err := MasterDB.Insert(article)
243+
if err != nil {
244+
objLog.Errorln("insert article error:", err)
245+
return err
246+
}
247+
248+
change := map[string]interface{}{
249+
"url": article.Id,
250+
}
251+
MasterDB.Table(new(model.Article)).Id(article.Id).Update(change)
252+
253+
return nil
254+
}
255+
215256
func (ArticleLogic) cleanUrl(articleUrl string, auto bool) string {
216257
pos := strings.LastIndex(articleUrl, "#")
217258
if pos > 0 {
@@ -422,7 +463,10 @@ func (ArticleLogic) Modify(ctx context.Context, user *model.Me, form url.Values)
422463
change := make(map[string]string)
423464

424465
for _, field := range fields {
425-
change[field] = form.Get(field)
466+
val := form.Get(field)
467+
if val != "" {
468+
change[field] = form.Get(field)
469+
}
426470
}
427471

428472
id := form.Get("id")

src/model/article.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ package model
88

99
import (
1010
"encoding/json"
11+
"strconv"
1112

13+
"github.com/go-xorm/xorm"
1214
"github.com/polaris1119/logger"
1315
)
1416

@@ -45,6 +47,14 @@ type Article struct {
4547
OpUser string `json:"op_user"`
4648
Ctime OftenTime `json:"ctime" xorm:"created"`
4749
Mtime OftenTime `json:"mtime" xorm:"<-"`
50+
51+
IsSelf bool `json:"is_self" xorm:"-"`
52+
}
53+
54+
func (this *Article) AfterSet(name string, cell xorm.Cell) {
55+
if name == "id" {
56+
this.IsSelf = strconv.Itoa(this.Id) == this.Url
57+
}
4858
}
4959

5060
func (*Article) TableName() string {

src/vendor/manifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@
263263
{
264264
"importpath": "github.com/polaris1119/echoutils",
265265
"repository": "https://github.com/polaris1119/echoutils",
266-
"revision": "7395877a70801d90de1913641b3590a54a1a2995",
266+
"revision": "5e14d4b37f74bad4fee32be3674c6d7bf7c1f5c3",
267267
"branch": "master"
268268
},
269269
{

static/ckeditor/config.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
var MyEditorExtraPlugins = 'codesnippet,image2,uploadimage,notification,widget,lineutils,justify,autolink';
2+
3+
var MyEditorConfig = {
4+
title: 'Go语言中文网富文本编辑器',
5+
// Define the toolbar: http://docs.ckeditor.com/#!/guide/dev_toolbar
6+
// The standard preset from CDN which we used as a base provides more features than we need.
7+
// Also by default it comes with a 2-line toolbar. Here we put all buttons in a single row.
8+
// toolbar: [
9+
// { name: 'basicstyles', items: [ 'Bold', 'Italic', 'Underline', 'RemoveFormat' ] },
10+
// { name: 'paragraph', items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight' ] },
11+
// { name: 'clipboard', items: [ 'Undo', 'Redo' ] },
12+
// { name: 'links', items: [ 'Link', 'Unlink' ] },
13+
// { name: 'insert', items: [ 'CodeSnippet', 'Image' ] },
14+
// { name: 'styles', items: [ 'Format' ] },
15+
// { name: 'document', items: [ 'Source', 'Preview' ] },
16+
// { name: 'tools', items: [ 'Maximize' ] }
17+
// ],
18+
startupFocus: true,
19+
// Since we define all configuration options here, let's instruct CKEditor to not load config.js which it does by default.
20+
// One HTTP request less will result in a faster startup time.
21+
// For more information check http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-customConfig
22+
customConfig: '',
23+
// Enabling extra plugins, available in the standard-all preset: http://ckeditor.com/presets-all
24+
// extraPlugins: 'sourcedialog,preview,codesnippet,image2,uploadimage,notification,prism,widget,lineutils,justify,autolink',
25+
// Remove the default image plugin because image2, which offers captions for images, was enabled above.
26+
removePlugins: 'image',
27+
filebrowserImageUploadUrl: '/image/quick_upload?command=QuickUpload&type=Images',
28+
29+
// See http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-codeSnippet_theme
30+
codeSnippet_theme: 'monokai_sublime',//'ir_black',
31+
codeSnippet_languages: {
32+
go: 'Go',
33+
php: 'PHP',
34+
bash: 'Bash',
35+
cpp: 'C/C++',
36+
json: 'JSON',
37+
html: 'HTML',
38+
http: 'HTTP',
39+
ini: 'INI',
40+
java: 'Java',
41+
javascript: 'JavaScript',
42+
markdown: 'Markdown',
43+
nginx: 'Nginx',
44+
sql: 'SQL',
45+
yaml: 'YAML',
46+
armasm: 'ARM Assembly'
47+
},
48+
/*********************** File management support ***********************/
49+
// In order to turn on support for file uploads, CKEditor has to be configured to use some server side
50+
// solution with file upload/management capabilities, like for example CKFinder.
51+
// For more information see http://docs.ckeditor.com/#!/guide/dev_ckfinder_integration
52+
// Uncomment and correct these lines after you setup your local CKFinder instance.
53+
// filebrowserBrowseUrl: 'http://example.com/ckfinder/ckfinder.html',
54+
// filebrowserUploadUrl: 'http://example.com/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Files',
55+
/*********************** File management support ***********************/
56+
// Make the editing area bigger than default.
57+
height: 361,
58+
width: '98%',
59+
// An array of stylesheets to style the WYSIWYG area.
60+
// Note: it is recommended to keep your own styles in a separate file in order to make future updates painless.
61+
// contentsCss: [ 'https://cdn.ckeditor.com/4.6.2/standard-all/contents.css', 'mystyles.css' ],
62+
// Reduce the list of block elements listed in the Format dropdown to the most commonly used.
63+
format_tags: 'p;h1;h2;h3;h4;pre',
64+
// Simplify the Image and Link dialog windows. The "Advanced" tab is not needed in most cases.
65+
removeDialogTabs: 'image:advanced;link:advanced;link:target'
66+
}

static/ckeditor/plugins/autosave/css/autosave.min.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)