浏览代码

实现文档历史

Minho 8 年之前
父节点
当前提交
056edb71fc

+ 5 - 1
controllers/base.go

@@ -18,6 +18,7 @@ type BaseController struct {
 	Member *models.Member
 	Option map[string]string
 	EnableAnonymous bool
+	EnableDocumentHistory bool
 }
 
 // Prepare 预处理.
@@ -25,7 +26,7 @@ func (c *BaseController) Prepare (){
 	c.Data["SiteName"] = "MinDoc"
 	c.Data["Member"] = models.Member{}
 	c.EnableAnonymous = false
-
+	c.EnableDocumentHistory = false
 
 	if member,ok := c.GetSession(conf.LoginSessionName).(models.Member); ok && member.MemberId > 0{
 		c.Member = &member
@@ -45,6 +46,9 @@ func (c *BaseController) Prepare (){
 			if strings.EqualFold(item.OptionName,"ENABLE_ANONYMOUS") && item.OptionValue == "true" {
 				c.EnableAnonymous = true
 			}
+			if strings.EqualFold(item.OptionName,"ENABLE_DOCUMENT_HISTORY") && item.OptionValue == "true" {
+				c.EnableDocumentHistory = true
+			}
 		}
 	}
 }

+ 83 - 0
controllers/document.go

@@ -21,6 +21,7 @@ import (
 	"github.com/lifei6671/godoc/conf"
 	"github.com/lifei6671/godoc/models"
 	"github.com/lifei6671/godoc/utils/wkhtmltopdf"
+	"github.com/lifei6671/godoc/utils"
 )
 
 //DocumentController struct.
@@ -674,6 +675,18 @@ func (c *DocumentController) Content() {
 			beego.Info("%d|", version, doc.Version)
 			c.JsonResult(6005, "文档已被修改确定要覆盖吗?")
 		}
+		history := models.NewDocumentHistory()
+		history.DocumentId = doc_id
+		history.Content = doc.Content
+		history.Markdown = doc.Markdown
+		history.DocumentName = doc.DocumentName
+		history.ModifyAt = c.Member.MemberId
+		history.MemberId = doc.MemberId
+		history.ParentId = doc.ParentId
+		history.Version = time.Now().Unix()
+		history.Action = "modify"
+		history.ActionName = "修改文档"
+
 		if markdown == "" && content != "" {
 			doc.Markdown = content
 		} else {
@@ -685,6 +698,13 @@ func (c *DocumentController) Content() {
 			beego.Error("InsertOrUpdate => ", err)
 			c.JsonResult(6006, "保存失败")
 		}
+		//如果启用了文档历史,则添加历史文档
+		if c.EnableDocumentHistory {
+			_,err = history.InsertOrUpdate()
+			if err != nil {
+				beego.Error("DocumentHistory InsertOrUpdate => ",err)
+			}
+		}
 
 		c.JsonResult(0, "ok", doc)
 	}
@@ -868,6 +888,69 @@ func (c *DocumentController) Search()  {
 	c.JsonResult(0,"ok",docs)
 }
 
+//文档历史列表.
+func (c *DocumentController) History() {
+	c.Prepare()
+
+	identify := c.GetString("identify")
+	doc_id, err := c.GetInt("doc_id", 0)
+	pageIndex, _ := c.GetInt("page", 1)
+
+	book_id := 0
+	//如果是超级管理员则忽略权限判断
+	if c.Member.Role == conf.MemberSuperRole {
+		book, err := models.NewBook().FindByFieldFirst("identify", identify)
+		if err != nil {
+			beego.Error("FindByIdentify => ", err)
+			c.JsonResult(6002, "项目不存在或权限不足")
+		}
+		book_id = book.BookId
+	} else {
+		bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
+
+		if err != nil || bookResult.RoleId == conf.BookObserver {
+			beego.Error("FindByIdentify => ", err)
+			c.JsonResult(6002, "项目不存在或权限不足")
+		}
+		book_id = bookResult.BookId
+	}
+
+	if doc_id <= 0 {
+		c.JsonResult(6001, "参数错误")
+	}
+
+	doc, err := models.NewDocument().Find(doc_id)
+
+	if err != nil {
+		beego.Error("Delete => ", err)
+		c.JsonResult(6003, "获取历史失败")
+	}
+	//如果文档所属项目错误
+	if doc.BookId != book_id {
+		c.JsonResult(6004, "参数错误")
+	}
+
+	historis,totalCount,err := models.NewDocumentHistory().FindToPager(doc_id,pageIndex,conf.PageSize)
+
+	if err != nil {
+		c.JsonResult(6005,"获取历史失败")
+	}
+	var data struct {
+		PageHtml string `json:"page_html"`
+		List []*models.DocumentHistorySimpleResult `json:"lists"`
+	}
+	data.List = historis
+	if totalCount > 0 {
+		html := utils.GetPagerHtml(c.Ctx.Request.RequestURI, pageIndex, conf.PageSize, totalCount)
+
+		data.PageHtml = string(html)
+	}else {
+		data.PageHtml = ""
+	}
+
+	c.JsonResult(0,"ok",data)
+}
+
 //递归生成文档序列数组.
 func RecursiveFun(parent_id int, prefix, dpath string, c *DocumentController, book *models.BookResult, docs []*models.Document, paths *list.List) {
 	for _, item := range docs {

+ 1 - 0
models/document.go

@@ -98,6 +98,7 @@ func (m *Document) RecursiveDocument(doc_id int) error {
 
 	if doc, err := m.Find(doc_id); err == nil {
 		o.Delete(doc)
+		NewDocumentHistory().Clear(doc.DocumentId)
 	}
 
 	var docs []*Document

+ 81 - 5
models/document_history.go

@@ -2,12 +2,15 @@ package models
 
 import (
 	"time"
-	"github.com/lifei6671/godoc/conf"
+
 	"github.com/astaxie/beego/orm"
+	"github.com/lifei6671/godoc/conf"
 )
 
 type DocumentHistory struct {
 	HistoryId    int       `orm:"column(history_id);pk;auto;unique" json:"history_id"`
+	Action       string    `orm:"column(action);size(255)" json:"action"`
+	ActionName   string    `orm:"column(action_name);size(255)" json:"action_name"`
 	DocumentId   int       `orm:"column(document_id);type(int);index" json:"doc_id"`
 	DocumentName string    `orm:"column(document_name);size(500)" json:"doc_name"`
 	ParentId     int       `orm:"column(parent_id);type(int);index;default(0)" json:"parent_id"`
@@ -19,6 +22,17 @@ type DocumentHistory struct {
 	Version      int64     `orm:"type(bigint);column(version)" json:"version"`
 }
 
+type DocumentHistorySimpleResult struct {
+	HistoryId    int                `json:"history_id"`
+	ActionName   string             `json:"action_name"`
+	MemberId     int 		`json:"member_id"`
+	Account      string 		`json:"account"`
+	ModifyAt     int 		`json:"modify_at"`
+	ModifyName   string 		`json:"modify_name"`
+	ModifyTime   time.Time 		`json:"modify_time"`
+	Version      int64 		`json:"version"`
+}
+
 // TableName 获取对应数据库表名.
 func (m *DocumentHistory) TableName() string {
 	return "document_history"
@@ -33,21 +47,83 @@ func (m *DocumentHistory) TableNameWithPrefix() string {
 	return conf.GetDatabasePrefix() + m.TableName()
 }
 
+func NewDocumentHistory() *DocumentHistory {
+	return &DocumentHistory{}
+}
+//清空指定文档的历史.
+func (m *DocumentHistory) Clear(doc_id int) error {
+	o := orm.NewOrm()
+
+	_, err := o.Raw("DELETE md_document_history WHERE document_id = ?", doc_id).Exec()
+
+	return err
+}
+
+//删除历史.
+func (m *DocumentHistory) Delete(history_id int) error {
+	o := orm.NewOrm()
+
+	_, err := o.Raw("DELETE md_document_history WHERE history_id = ?", history_id).Exec()
+	return err
+}
+
+//恢复指定历史的文档.
+func (m *DocumentHistory) Restore(history_id int) error {
+	o := orm.NewOrm()
+
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter("history_id", history_id).One(m)
+
+	if err != nil {
+		return err
+	}
+	doc, err := NewDocument().Find(m.DocumentId)
+
+	if err != nil {
+		return err
+	}
+	doc.DocumentName = m.DocumentName
+	doc.Content = m.Content
+	doc.Markdown = m.Markdown
+	doc.Release = m.Content
+
+	_, err = o.Update(doc)
+
+	return err
+}
+
+func (m *DocumentHistory) InsertOrUpdate() (history *DocumentHistory,err error)  {
+	o := orm.NewOrm()
+	history = m
 
-func (m *DocumentHistory) FindToPager(doc_id,page_index,page_size int) (docs []*DocumentHistory,totalCount int,err error) {
+	if m.HistoryId > 0 {
+		_,err = o.Update(m)
+	}else{
+		_,err = o.Insert(m)
+	}
+	return
+}
+//分页查询指定文档的历史.
+func (m *DocumentHistory) FindToPager(doc_id, page_index, page_size int) (docs []*DocumentHistorySimpleResult, totalCount int, err error) {
 
 	o := orm.NewOrm()
 
 	offset := (page_index - 1) * page_size
 
 	totalCount = 0
-	_,err = o.QueryTable(m.TableNameWithPrefix()).Filter("document_id",doc_id).Offset(offset).Limit(page_size).All(docs)
+
+	sql := `SELECT history.*,m1.account,m2.account as ModifyName
+FROM md_document_history AS history
+LEFT JOIN md_members AS m1 ON history.member_id = m1.member_id
+LEFT JOIN md_members AS m2 ON history.member_id = m2.member_id
+WHERE history.document_id = ? ORDER BY history.history_id DESC LIMIT ?,?;`
+
+	_, err = o.Raw(sql,doc_id,offset,page_size).QueryRows(&docs)
 
 	if err != nil {
 		return
 	}
 	var count int64
-	count,err = o.QueryTable(m.TableNameWithPrefix()).Filter("document_id",doc_id).Count()
+	count, err = o.QueryTable(m.TableNameWithPrefix()).Filter("document_id", doc_id).Count()
 
 	if err != nil {
 		return
@@ -55,4 +131,4 @@ func (m *DocumentHistory) FindToPager(doc_id,page_index,page_size int) (docs []*
 	totalCount = int(count)
 
 	return
-}
+}

+ 1 - 0
routers/router.go

@@ -57,6 +57,7 @@ func init()  {
 	beego.Router("/api/:key/create",&controllers.DocumentController{},"post:Create")
 	beego.Router("/api/:key/delete", &controllers.DocumentController{},"post:Delete")
 	beego.Router("/api/:key/content/?:id",&controllers.DocumentController{},"*:Content")
+	beego.Router("/api/history", &controllers.DocumentController{},"get:History")
 
 
 	beego.Router("/docs/:key", &controllers.DocumentController{},"*:Index")

+ 28 - 0
static/js/editor.js

@@ -199,3 +199,31 @@ function showSuccess($msg,$id) {
     $($id).addClass("success-message").removeClass("error-message").text($msg);
     return true;
 }
+
+$(function () {
+    $("#documentHistoryModal").on("shown.bs.modal",function () {
+        var historyVue = new Vue({
+            el : "#documentHistoryModal",
+            data : {
+                lists : []
+            },
+            delimiters : ['${','}'],
+            methods : {
+
+            }
+        });
+
+        $.ajax({
+            url : window.historyURL,
+            data : { "identify" : window.book.identify,"doc_id" : window.selectNode.id },
+            dataType :"json",
+            success : function (res) {
+                if(res.errcode === 0){
+                    historyVue.lists = res.data.lists;
+                }else{
+                    alert(res.message);
+                }
+            }
+        });
+    });
+});

+ 1 - 1
static/js/markdown.js

@@ -61,7 +61,7 @@ $(function () {
        if(name === "attachment"){
            $("#uploadAttachModal").modal("show");
        }else if(name === "history"){
-
+            $("#documentHistoryModal").modal("show");
        }else if(name === "save"){
             saveDocument(false);
 

+ 43 - 2
views/document/markdown_edit_template.tpl

@@ -17,6 +17,7 @@
         window.editURL = "{{urlfor "DocumentController.Content" ":key" .Model.Identify ":id" ""}}";
         window.releaseURL = "{{urlfor "BookController.Release" ":key" .Model.Identify}}";
         window.sortURL = "{{urlfor "BookController.SaveSort" ":key" .Model.Identify}}";
+        window.historyURL = "{{urlfor "DocumentController.History"}}";
     </script>
     <!-- Bootstrap -->
     <link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
@@ -80,7 +81,7 @@
 
         <div class="editormd-group pull-right">
             <a href="javascript:;" data-toggle="tooltip" data-title="关闭实时预览"><i class="fa fa-eye-slash first" name="watch" unselectable="on"></i></a>
-            {{/*<a href="javascript:;" data-toggle="tooltip" data-title="修改历史"><i class="fa fa-history item" name="history" aria-hidden="true"></i></a>*/}}
+            <a href="javascript:;" data-toggle="tooltip" data-title="修改历史"><i class="fa fa-history item" name="history" aria-hidden="true"></i></a>
             <a href="javascript:;" data-toggle="tooltip" data-title="边栏"><i class="fa fa-columns item" aria-hidden="true" name="sidebar"></i></a>
             <a href="javascript:;" data-toggle="tooltip" data-title="使用帮助"><i class="fa fa-question-circle-o last" aria-hidden="true" name="help"></i></a>
         </div>
@@ -205,6 +206,46 @@
         </form>
     </div>
 </div>
+<!-- Modal -->
+<div class="modal fade" id="documentHistoryModal" tabindex="-1" role="dialog" aria-labelledby="documentHistoryModalModalLabel">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                <h4 class="modal-title">文档历史记录</h4>
+            </div>
+            <div class="modal-body text-center">
+                <template v-if="lists.length <= 0">
+                    暂无数据
+                </template>
+                <template v-else>
+                    <table class="table">
+                        <thead>
+                        <tr>
+                            <th>#</th><th>名称</th><th>修改时间</th><th>修改人</th><th>版本</th><th>操作</th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        <template v-for="item in lists">
+                        <tr>
+                            <td>${item.history_id}</td>
+                            <td>${item.action_name}</td>
+                            <td>${item.modify_time}</td>
+                            <td>${item.modify_name}</td>
+                            <td>${item.version}</td>
+                            <td></td>
+                        </tr>
+                        </template>
+                        </tbody>
+                    </table>
+                </template>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
 
 <script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
 <script src="{{cdnjs "/static/vuejs/vue.min.js"}}" type="text/javascript"></script>
@@ -212,7 +253,7 @@
 <script src="{{cdnjs "/static/webuploader/webuploader.min.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/jstree/3.3.4/jstree.min.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/editor.md/editormd.js"}}" type="text/javascript"></script>
-<script type="text/javascript" src="{{cdnjs "/static/layer/layer.js"}}"></script>
+<script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript" ></script>
 <script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
 <script type="text/javascript">
     window.vueApp = new Vue({