document.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. package controllers
  2. import (
  3. "os"
  4. "time"
  5. "regexp"
  6. "strconv"
  7. "strings"
  8. "net/http"
  9. "path/filepath"
  10. "encoding/json"
  11. "html/template"
  12. "container/list"
  13. "github.com/lifei6671/godoc/models"
  14. "github.com/lifei6671/godoc/conf"
  15. "github.com/astaxie/beego"
  16. "github.com/astaxie/beego/orm"
  17. "github.com/lifei6671/godoc/utils"
  18. )
  19. //DocumentController struct.
  20. type DocumentController struct {
  21. BaseController
  22. }
  23. //判断用户是否可以阅读文档.
  24. func isReadable (identify,token string,c *DocumentController) *models.BookResult {
  25. book, err := models.NewBook().FindByFieldFirst("identify", identify)
  26. if err != nil {
  27. beego.Error(err)
  28. c.Abort("500")
  29. }
  30. //如果文档是私有的
  31. if book.PrivatelyOwned == 1 {
  32. is_ok := false
  33. if c.Member != nil {
  34. _, err := models.NewRelationship().FindForRoleId(book.BookId, c.Member.MemberId)
  35. if err == nil {
  36. is_ok = true
  37. }
  38. }
  39. if book.PrivateToken != "" && !is_ok {
  40. //如果有访问的Token,并且该项目设置了访问Token,并且和用户提供的相匹配,则记录到Session中.
  41. //如果用户未提供Token且用户登录了,则判断用户是否参与了该项目.
  42. //如果用户未登录,则从Session中读取Token.
  43. if token != "" && strings.EqualFold(token, book.PrivateToken) {
  44. c.SetSession(identify, token)
  45. } else if token, ok := c.GetSession(identify).(string); !ok || !strings.EqualFold(token, book.PrivateToken) {
  46. c.Abort("403")
  47. }
  48. } else if !is_ok {
  49. c.Abort("403")
  50. }
  51. }
  52. bookResult := book.ToBookResult()
  53. if c.Member != nil {
  54. rel, err := models.NewRelationship().FindByBookIdAndMemberId(bookResult.BookId, c.Member.MemberId)
  55. if err == nil {
  56. bookResult.MemberId = rel.MemberId
  57. bookResult.RoleId = rel.RoleId
  58. bookResult.RelationshipId = rel.RelationshipId
  59. }
  60. }
  61. //判断是否需要显示评论框
  62. if bookResult.CommentStatus == "closed" {
  63. bookResult.IsDisplayComment = false
  64. } else if bookResult.CommentStatus == "open" {
  65. bookResult.IsDisplayComment = true
  66. } else if bookResult.CommentStatus == "group_only" {
  67. bookResult.IsDisplayComment = bookResult.RelationshipId > 0
  68. } else if bookResult.CommentStatus == "registered_only" {
  69. bookResult.IsDisplayComment = true
  70. }
  71. return bookResult
  72. }
  73. //文档首页.
  74. func (c *DocumentController) Index() {
  75. c.Prepare()
  76. identify := c.Ctx.Input.Param(":key")
  77. token := c.GetString("token")
  78. if identify == "" {
  79. c.Abort("404")
  80. }
  81. //如果没有开启你们访问则跳转到登录
  82. if !c.EnableAnonymous && c.Member == nil {
  83. c.Redirect(beego.URLFor("AccountController.Login"),302)
  84. return
  85. }
  86. bookResult := isReadable(identify,token,c)
  87. c.TplName = "document/" + bookResult.Theme + "_read.tpl"
  88. tree,err := models.NewDocument().CreateDocumentTreeForHtml(bookResult.BookId,0)
  89. if err != nil {
  90. beego.Error(err)
  91. c.Abort("500")
  92. }
  93. c.Data["Model"] = bookResult
  94. c.Data["Result"] = template.HTML(tree)
  95. c.Data["Title"] = "概要"
  96. c.Data["Content"] = bookResult.Description
  97. }
  98. //阅读文档.
  99. func (c *DocumentController) Read() {
  100. c.Prepare()
  101. identify := c.Ctx.Input.Param(":key")
  102. token := c.GetString("token")
  103. id := c.GetString(":id")
  104. if identify == "" || id == ""{
  105. c.Abort("404")
  106. }
  107. //如果没有开启你们访问则跳转到登录
  108. if !c.EnableAnonymous && c.Member == nil {
  109. c.Redirect(beego.URLFor("AccountController.Login"),302)
  110. return
  111. }
  112. bookResult := isReadable(identify,token,c)
  113. c.TplName = "document/" + bookResult.Theme + "_read.tpl"
  114. doc := models.NewDocument()
  115. if doc_id,err := strconv.Atoi(id);err == nil {
  116. doc,err = doc.Find(doc_id)
  117. if err != nil {
  118. beego.Error(err)
  119. c.Abort("500")
  120. }
  121. }else{
  122. doc,err = doc.FindByFieldFirst("identify",id)
  123. if err != nil {
  124. beego.Error(err)
  125. c.Abort("500")
  126. }
  127. }
  128. if doc.BookId != bookResult.BookId {
  129. c.Abort("403")
  130. }
  131. if c.IsAjax() {
  132. var data struct{
  133. DocTitle string `json:"doc_title"`
  134. Body string `json:"body"`
  135. Title string `json:"title"`
  136. }
  137. data.DocTitle = doc.DocumentName
  138. data.Body = doc.Release
  139. data.Title = doc.DocumentName + " - Powered by MinDoc"
  140. c.JsonResult(0,"ok",data)
  141. }
  142. tree,err := models.NewDocument().CreateDocumentTreeForHtml(bookResult.BookId,doc.DocumentId)
  143. if err != nil {
  144. beego.Error(err)
  145. c.Abort("500")
  146. }
  147. c.Data["Model"] = bookResult
  148. c.Data["Result"] = template.HTML(tree)
  149. c.Data["Title"] = doc.DocumentName
  150. c.Data["Content"] = template.HTML(doc.Release)
  151. }
  152. //编辑文档.
  153. func (c *DocumentController) Edit() {
  154. c.Prepare()
  155. identify := c.Ctx.Input.Param(":key")
  156. if identify == "" {
  157. c.Abort("404")
  158. }
  159. bookResult,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
  160. if err != nil {
  161. beego.Error("DocumentController.Edit => ",err)
  162. c.Abort("403")
  163. }
  164. if bookResult.RoleId == conf.BookObserver {
  165. c.JsonResult(6002,"项目不存在或权限不足")
  166. }
  167. //根据不同编辑器类型加载编辑器
  168. if bookResult.Editor == "markdown" {
  169. c.TplName = "document/markdown_edit_template.tpl"
  170. }else if bookResult.Editor == "html"{
  171. c.TplName = "document/html_edit_template.tpl"
  172. }else{
  173. c.TplName = "document/" + bookResult.Editor + "_edit_template.tpl"
  174. }
  175. c.Data["Model"] = bookResult
  176. r,_ := json.Marshal(bookResult)
  177. c.Data["ModelResult"] = template.JS(string(r))
  178. c.Data["Result"] = template.JS("[]")
  179. trees ,err := models.NewDocument().FindDocumentTree(bookResult.BookId)
  180. if err != nil {
  181. beego.Error("FindDocumentTree => ", err)
  182. }else{
  183. if len(trees) > 0 {
  184. if jtree, err := json.Marshal(trees); err == nil {
  185. c.Data["Result"] = template.JS(string(jtree))
  186. }
  187. }else{
  188. c.Data["Result"] = template.JS("[]")
  189. }
  190. }
  191. }
  192. //创建一个文档.
  193. func (c *DocumentController) Create() {
  194. identify := c.GetString("identify")
  195. doc_identify := c.GetString("doc_identify")
  196. doc_name := c.GetString("doc_name")
  197. parent_id,_ := c.GetInt("parent_id",0)
  198. doc_id,_ := c.GetInt("doc_id",0)
  199. if identify == "" {
  200. c.JsonResult(6001,"参数错误")
  201. }
  202. if doc_name == "" {
  203. c.JsonResult(6004,"文档名称不能为空")
  204. }
  205. if doc_identify != "" {
  206. if ok, err := regexp.MatchString(`^[a-z]+[a-zA-Z0-9_\-]*$`, doc_identify); !ok || err != nil {
  207. c.JsonResult(6003, "文档标识只能包含小写字母、数字,以及“-”和“_”符号,并且只能小写字母开头")
  208. }
  209. d,_ := models.NewDocument().FindByFieldFirst("identify",doc_identify);
  210. if d.DocumentId > 0 && d.DocumentId != doc_id{
  211. c.JsonResult(6006,"文档标识已被使用")
  212. }
  213. }
  214. bookResult,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
  215. if err != nil || bookResult.RoleId == conf.BookObserver {
  216. beego.Error("FindByIdentify => ",err)
  217. c.JsonResult(6002,"项目不存在或权限不足")
  218. }
  219. if parent_id > 0 {
  220. doc,err := models.NewDocument().Find(parent_id)
  221. if err != nil || doc.BookId != bookResult.BookId{
  222. c.JsonResult(6003,"父分类不存在")
  223. }
  224. }
  225. document,_ := models.NewDocument().Find(doc_id)
  226. document.MemberId = c.Member.MemberId
  227. document.BookId = bookResult.BookId
  228. if doc_identify != ""{
  229. document.Identify = doc_identify
  230. }
  231. document.Version = time.Now().Unix()
  232. document.DocumentName = doc_name
  233. document.ParentId = parent_id
  234. if err := document.InsertOrUpdate();err != nil {
  235. beego.Error("InsertOrUpdate => ",err)
  236. c.JsonResult(6005,"保存失败")
  237. }else{
  238. c.JsonResult(0,"ok",document)
  239. }
  240. }
  241. //上传附件或图片.
  242. func (c *DocumentController) Upload() {
  243. identify := c.GetString("identify")
  244. doc_id,_ := c.GetInt("doc_id")
  245. if identify == "" {
  246. c.JsonResult(6001,"参数错误")
  247. }
  248. name := "editormd-file-file"
  249. file,moreFile,err := c.GetFile(name)
  250. if err == http.ErrMissingFile {
  251. name = "editormd-image-file"
  252. file,moreFile,err = c.GetFile(name);
  253. if err == http.ErrMissingFile {
  254. c.JsonResult(6003,"没有发现需要上传的文件")
  255. }
  256. }
  257. if err != nil {
  258. c.JsonResult(6002,err.Error())
  259. }
  260. defer file.Close()
  261. ext := filepath.Ext(moreFile.Filename)
  262. if ext == "" {
  263. c.JsonResult(6003,"无法解析文件的格式")
  264. }
  265. if !conf.IsAllowUploadFileExt(ext) {
  266. c.JsonResult(6004,"不允许的文件类型")
  267. }
  268. book,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
  269. if err != nil {
  270. beego.Error("DocumentController.Edit => ",err)
  271. if err == orm.ErrNoRows {
  272. c.JsonResult(6006,"权限不足")
  273. }
  274. c.JsonResult(6001,err.Error())
  275. }
  276. //如果没有编辑权限
  277. if book.RoleId != conf.BookEditor && book.RoleId != conf.BookAdmin && book.RoleId != conf.BookFounder {
  278. c.JsonResult(6006,"权限不足")
  279. }
  280. if doc_id > 0 {
  281. doc,err := models.NewDocument().Find(doc_id);
  282. if err != nil {
  283. c.JsonResult(6007,"文档不存在")
  284. }
  285. if doc.BookId != book.BookId {
  286. c.JsonResult(6008,"文档不属于指定的项目")
  287. }
  288. }
  289. fileName := "attachment_" + strconv.FormatInt(time.Now().UnixNano(), 16)
  290. filePath := "uploads/" + time.Now().Format("200601") + "/" + fileName + ext
  291. path := filepath.Dir(filePath)
  292. os.MkdirAll(path, os.ModePerm)
  293. err = c.SaveToFile(name,filePath)
  294. if err != nil {
  295. beego.Error("SaveToFile => ",err)
  296. c.JsonResult(6005,"保存文件失败")
  297. }
  298. attachment := models.NewAttachment()
  299. attachment.BookId = book.BookId
  300. attachment.FileName = moreFile.Filename
  301. attachment.CreateAt = c.Member.MemberId
  302. attachment.FileExt = ext
  303. attachment.FilePath = filePath
  304. if doc_id > 0{
  305. attachment.DocumentId = doc_id
  306. }
  307. if strings.EqualFold(ext,".jpg") || strings.EqualFold(ext,".jpeg") || strings.EqualFold(ext,"png") || strings.EqualFold(ext,"gif") {
  308. attachment.HttpPath = c.BaseUrl() + "/" + filePath
  309. }
  310. err = attachment.Insert();
  311. if err != nil {
  312. os.Remove(filePath)
  313. beego.Error("Attachment Insert => ",err)
  314. c.JsonResult(6006,"文件保存失败")
  315. }
  316. if attachment.HttpPath == "" {
  317. attachment.HttpPath = c.BaseUrl() + beego.URLFor("DocumentController.DownloadAttachment",":key", identify, ":attach_id", attachment.AttachmentId)
  318. if err := attachment.Update();err != nil {
  319. beego.Error("SaveToFile => ",err)
  320. c.JsonResult(6005,"保存文件失败")
  321. }
  322. }
  323. result := map[string]interface{}{
  324. "errcode" : 0,
  325. "success" : 1,
  326. "message" :"ok",
  327. "url" : attachment.HttpPath,
  328. "alt" : attachment.FileName,
  329. }
  330. c.Data["json"] = result
  331. c.ServeJSON(true)
  332. c.StopRun()
  333. }
  334. //DownloadAttachment 下载附件.
  335. func (c *DocumentController) DownloadAttachment() {
  336. c.Prepare()
  337. identify := c.Ctx.Input.Param(":key")
  338. attach_id,_ := strconv.Atoi(c.Ctx.Input.Param(":attach_id"))
  339. token := c.GetString("token")
  340. member_id := 0
  341. if c.Member != nil {
  342. member_id = c.Member.MemberId
  343. }
  344. book_id := 0
  345. //判断用户是否参与了项目
  346. bookResult,err := models.NewBookResult().FindByIdentify(identify,member_id)
  347. if err != nil {
  348. //判断项目公开状态
  349. book,err := models.NewBook().FindByFieldFirst("identify",identify)
  350. if err != nil {
  351. c.Abort("404")
  352. }
  353. //如果项目是私有的,并且token不正确
  354. if (book.PrivatelyOwned == 1 && token == "" ) || ( book.PrivatelyOwned == 1 && book.PrivateToken != token ){
  355. c.Abort("403")
  356. }
  357. book_id = book.BookId
  358. }else{
  359. book_id = bookResult.BookId
  360. }
  361. attachment,err := models.NewAttachment().Find(attach_id)
  362. if err != nil {
  363. beego.Error("DownloadAttachment => ", err)
  364. if err == orm.ErrNoRows {
  365. c.Abort("404")
  366. } else {
  367. c.Abort("500")
  368. }
  369. }
  370. if attachment.BookId != book_id {
  371. c.Abort("404")
  372. }
  373. c.Ctx.Output.Download(attachment.FilePath,attachment.FileName)
  374. c.StopRun()
  375. }
  376. //删除文档.
  377. func (c *DocumentController) Delete() {
  378. c.Prepare()
  379. identify := c.GetString("identify")
  380. doc_id,err := c.GetInt("doc_id",0)
  381. bookResult,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
  382. if err != nil || bookResult.RoleId == conf.BookObserver {
  383. beego.Error("FindByIdentify => ",err)
  384. c.JsonResult(6002,"项目不存在或权限不足")
  385. }
  386. if doc_id <= 0 {
  387. c.JsonResult(6001,"参数错误")
  388. }
  389. doc,err := models.NewDocument().Find(doc_id)
  390. if err != nil {
  391. beego.Error("Delete => ",err)
  392. c.JsonResult(6003,"删除失败")
  393. }
  394. if doc.BookId != bookResult.BookId {
  395. c.JsonResult(6004,"参数错误")
  396. }
  397. err = doc.RecursiveDocument(doc.DocumentId)
  398. if err != nil {
  399. c.JsonResult(6005,"删除失败")
  400. }
  401. //重置文档数量统计
  402. models.NewBook().ResetDocumentNumber(doc.BookId)
  403. c.JsonResult(0,"ok")
  404. }
  405. //获取文档内容.
  406. func (c *DocumentController) Content() {
  407. c.Prepare()
  408. identify := c.Ctx.Input.Param(":key")
  409. doc_id,err := c.GetInt("doc_id")
  410. if err != nil {
  411. doc_id,_ = strconv.Atoi(c.Ctx.Input.Param(":id"))
  412. }
  413. bookResult,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
  414. if err != nil || bookResult.RoleId == conf.BookObserver {
  415. beego.Error("FindByIdentify => ",err)
  416. c.JsonResult(6002,"项目不存在或权限不足")
  417. }
  418. if doc_id <= 0 {
  419. c.JsonResult(6001,"参数错误")
  420. }
  421. if c.Ctx.Input.IsPost() {
  422. markdown := strings.TrimSpace(c.GetString("markdown",""))
  423. content := c.GetString("html")
  424. version,_ := c.GetInt64("version",0)
  425. is_cover := c.GetString("cover")
  426. doc ,err := models.NewDocument().Find(doc_id);
  427. if err != nil {
  428. c.JsonResult(6003,"读取文档错误")
  429. }
  430. if doc.BookId != bookResult.BookId {
  431. c.JsonResult(6004,"保存的文档不属于指定项目")
  432. }
  433. if doc.Version != version && !strings.EqualFold(is_cover,"yes"){
  434. beego.Info("%d|",version,doc.Version)
  435. c.JsonResult(6005,"文档已被修改确定要覆盖吗?")
  436. }
  437. if markdown == "" && content != ""{
  438. doc.Markdown = content
  439. }else{
  440. doc.Markdown = markdown
  441. }
  442. doc.Version = time.Now().Unix()
  443. doc.Content = content
  444. if err := doc.InsertOrUpdate();err != nil {
  445. beego.Error("InsertOrUpdate => ",err)
  446. c.JsonResult(6006,"保存失败")
  447. }
  448. c.JsonResult(0,"ok",doc)
  449. }
  450. doc,err := models.NewDocument().Find(doc_id)
  451. if err != nil {
  452. c.JsonResult(6003,"文档不存在")
  453. }
  454. c.JsonResult(0,"ok",doc)
  455. }
  456. //导出文件
  457. func (c *DocumentController) Export() {
  458. c.Prepare()
  459. c.TplName = "document/export.tpl"
  460. identify := c.Ctx.Input.Param(":key")
  461. // token := c.GetString("token")
  462. output := c.GetString("output")
  463. //id, _ := c.GetInt(":id")
  464. token := c.GetString("token")
  465. if identify == "" {
  466. c.Abort("404")
  467. }
  468. //如果没有开启你们访问则跳转到登录
  469. if !c.EnableAnonymous && c.Member == nil {
  470. c.Redirect(beego.URLFor("AccountController.Login"),302)
  471. return
  472. }
  473. book := isReadable(identify,token,c)
  474. if book.PrivatelyOwned == 1 {
  475. }
  476. docs, err := models.NewDocument().FindListByBookId(book.BookId)
  477. if err != nil {
  478. beego.Error(err)
  479. c.Abort("500")
  480. }
  481. if output == "pdf" {
  482. dpath := "cache/" + book.Identify
  483. os.MkdirAll(dpath, 0766)
  484. pathList := list.New()
  485. RecursiveFun(0, "", dpath, c, book, docs, pathList)
  486. defer os.RemoveAll(dpath)
  487. os.MkdirAll("./cache", 0766)
  488. pdfpath := "cache/" + identify + ".pdf"
  489. paths := make([]string, len(docs))
  490. index := 0
  491. for e := pathList.Front(); e != nil; e = e.Next() {
  492. paths[index] = e.Value.(string)
  493. index ++
  494. }
  495. beego.Info(paths)
  496. utils.ConverterHtmlToPdf(paths, pdfpath)
  497. c.Ctx.Output.Download(pdfpath, identify + ".pdf")
  498. defer os.Remove(pdfpath)
  499. c.StopRun()
  500. }
  501. c.StopRun()
  502. }
  503. func RecursiveFun(parent_id int,prefix,dpath string,c *DocumentController,book *models.BookResult,docs []*models.Document,paths *list.List) {
  504. for _, item := range docs {
  505. if item.ParentId == parent_id {
  506. name := prefix + strconv.Itoa(item.ParentId) + strconv.Itoa(item.OrderSort) + strconv.Itoa(item.DocumentId)
  507. fpath := dpath + "/" + name + ".html"
  508. paths.PushBack(fpath)
  509. f, err := os.OpenFile(fpath, os.O_CREATE, 0666)
  510. if err != nil {
  511. beego.Error(err)
  512. c.Abort("500")
  513. }
  514. html, err := c.ExecuteViewPathTemplate("document/export.tpl", map[string]interface{}{"Model" : book, "Lists":item,"BaseUrl" : c.BaseUrl()})
  515. if err != nil {
  516. f.Close()
  517. beego.Error(err)
  518. c.Abort("500")
  519. }
  520. f.WriteString(html)
  521. f.Close()
  522. for _, sub := range docs {
  523. if sub.ParentId == item.DocumentId {
  524. RecursiveFun(item.DocumentId,name,dpath,c,book,docs,paths)
  525. break;
  526. }
  527. }
  528. }
  529. }
  530. }