相关文档

dev
truthhun 1 year ago
parent 0c589190e5
commit be9a852541

@ -51,8 +51,8 @@ message Document {
message DeleteDocumentRequest { repeated int64 id = 1; }
message RecoverRecycleDocumentRequest { repeated int64 id = 1; }
message GetDocumentRequest {
int64 id = 1;
message GetDocumentRequest {
int64 id = 1;
bool with_author = 2;
}
@ -74,7 +74,7 @@ message ListDocumentReply {
repeated Document document = 2;
}
message CreateDocumentItem{
message CreateDocumentItem {
string title = 1;
int64 attachment_id = 2;
int32 price = 3;
@ -86,17 +86,14 @@ message CreateDocumentRequest {
repeated CreateDocumentItem document = 3;
}
message SetDocumentRecommendRequest{
message SetDocumentRecommendRequest {
repeated int64 id = 1;
int32 type = 2; // 0, 1: 2:
}
message ListDocumentForHomeRequest {
int64 limit = 1;
}
message ListDocumentForHomeRequest { int64 limit = 1; }
message ListDocumentForHomeItem{
message ListDocumentForHomeItem {
int64 category_id = 1;
string category_cover = 2;
string category_name = 3;
@ -112,9 +109,8 @@ message SearchDocumentRequest {
int32 size = 2;
string wd = 3;
repeated int64 category_id = 4; //
string sort = 5; //
string ext = 7; //
string sort = 5; //
string ext = 7; //
}
message SearchDocumentReply {
@ -123,24 +119,24 @@ message SearchDocumentReply {
repeated Document document = 3;
}
message DownloadDocumentReply {
string url = 1;
}
message DownloadDocumentReply { string url = 1; }
service DocumentAPI {
rpc ListDocumentForHome(ListDocumentForHomeRequest) returns ( ListDocumentForHomeResponse) {
rpc ListDocumentForHome(ListDocumentForHomeRequest)
returns (ListDocumentForHomeResponse) {
option (google.api.http) = {
get : '/api/v1/document/home',
};
}
rpc SetDocumentRecommend(SetDocumentRecommendRequest) returns (google.protobuf.Empty) {
rpc SetDocumentRecommend(SetDocumentRecommendRequest)
returns (google.protobuf.Empty) {
option (google.api.http) = {
put : '/api/v1/document/recommend',
body : '*',
};
}
rpc CreateDocument(CreateDocumentRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
post : '/api/v1/document',
@ -167,6 +163,12 @@ service DocumentAPI {
};
}
rpc GetRelatedDocuments(Document) returns (ListDocumentReply) {
option (google.api.http) = {
get : '/api/v1/document/related',
};
}
rpc DownloadDocument(Document) returns (DownloadDocumentReply) {
option (google.api.http) = {
get : '/api/v1/document/download',
@ -178,7 +180,7 @@ service DocumentAPI {
get : '/api/v1/document/list',
};
}
rpc SearchDocument(SearchDocumentRequest) returns (SearchDocumentReply) {
option (google.api.http) = {
get : '/api/v1/document/search',

@ -654,3 +654,10 @@ func (s *DocumentAPIService) generateDownloadURL(document model.Document, cfg mo
}
return fmt.Sprintf("/download/%s?filename=%s", tokenString, url.QueryEscape(document.Title+document.Ext)), nil
}
func (s *DocumentAPIService) GetRelatedDocuments(ctx context.Context, req *pb.Document) (res *pb.ListDocumentReply, err error) {
docs, _ := s.dbModel.GetRelatedDocuments(req.Id)
res = &pb.ListDocumentReply{}
util.CopyStruct(&docs, &res.Document)
return res, nil
}

@ -220,11 +220,13 @@ const (
ConfigSecurityEnableCaptchaRegister = "enable_captcha_register" // 是否开启注册验证码
ConfigSecurityEnableCaptchaComment = "enable_captcha_comment" // 是否开启注册验证码
ConfigSecurityEnableCaptchaFindPassword = "enable_captcha_find_password" // 是否开启注册验证码
ConfigSecurityDocumentRelatedDuration = "document_related_duration" // 相关文档有效期默认为7天最小值为1
)
type ConfigSecurity struct {
MaxDocumentSize int32 `json:"max_document_size"` // 允许上传的最大文档大小
CommentInterval int32 `json:"comment_interval"` // 评论时间间隔, 单位秒
DocumentRelatedDuration int32 `json:"document_related_duration"` // 相关文档有效期默认为7天最小值为1
IsClose bool `json:"is_close"` // 是否闭站
CloseStatement string `json:"close_statement"` // 闭站说明
EnableRegister bool `json:"enable_register"` // 是否启用注册
@ -454,7 +456,7 @@ func (m *DBModel) GetConfigOfSecurity(name ...string) (config ConfigSecurity) {
case "is_close", "enable_register", "enable_captcha_login", "enable_captcha_register", "enable_captcha_comment", "enable_captcha_find_password", "enable_captcha_upload":
value, _ := strconv.ParseBool(cfg.Value)
data[cfg.Name] = value
case "max_document_size", "comment_interval":
case "max_document_size", "comment_interval", ConfigSecurityDocumentRelatedDuration:
data[cfg.Name], _ = strconv.Atoi(cfg.Value)
default:
data[cfg.Name] = cfg.Value
@ -544,6 +546,7 @@ func (m *DBModel) initConfig() (err error) {
// 安全配置项
{Category: ConfigCategorySecurity, Name: ConfigSecurityMaxDocumentSize, Label: "最大文档大小(MB)", Value: "50", Placeholder: "允许用户上传的最大文档大小默认为50即50MB", InputType: "number", Sort: 15, Options: ""},
{Category: ConfigCategorySecurity, Name: ConfigSecurityCommentInterval, Label: "评论时间间隔", Value: "10", Placeholder: "用户评论时间间隔单位为秒。0表示不限制。", InputType: "number", Sort: 15, Options: ""},
{Category: ConfigCategorySecurity, Name: ConfigSecurityDocumentRelatedDuration, Label: "文档的【相关文档】有效期", Value: "7", Placeholder: "文档的相关联文档的有效期默认为7即7天0或小于0表示不开启相关文档功能", InputType: "number", Sort: 15, Options: ""},
{Category: ConfigCategorySecurity, Name: ConfigSecurityIsClose, Label: "是否关闭网站", Value: "false", Placeholder: "请选择是否关闭网站", InputType: "switch", Sort: 16, Options: ""},
{Category: ConfigCategorySecurity, Name: ConfigSecurityCloseStatement, Label: "闭站说明", Value: "false", Placeholder: "关闭网站后,页面提示的内容", InputType: "textarea", Sort: 17, Options: ""},
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableRegister, Label: "是否允许注册", Value: "true", Placeholder: "请选择是否允许用户注册", InputType: "switch", Sort: 18, Options: ""},

@ -0,0 +1,187 @@
package model
import (
"moredoc/util"
"strings"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
)
type DocumentRelate struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
DocumentId int64 `form:"document_id" json:"document_id,omitempty" gorm:"column:document_id;type:bigint(20);size:20;default:0;comment:;index:idx_document_id,unique"`
RelatedDocumentId string `form:"related_document_id" json:"related_document_id,omitempty" gorm:"column:related_document_id;type:text;comment:;"`
CreatedAt *time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;comment:创建时间;"`
UpdatedAt *time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;comment:更新时间;"`
}
func (DocumentRelate) TableName() string {
return tablePrefix + "document_relate"
}
// CreateDocumentRelate 创建DocumentRelate
func (m *DBModel) CreateDocumentRelate(documentRelate *DocumentRelate) (err error) {
err = m.db.Create(documentRelate).Error
if err != nil {
m.logger.Error("CreateDocumentRelate", zap.Error(err))
return
}
return
}
// UpdateDocumentRelate 更新DocumentRelate如果需要更新指定字段则请指定updateFields参数
func (m *DBModel) UpdateDocumentRelate(documentRelate *DocumentRelate, updateFields ...string) (err error) {
db := m.db.Model(documentRelate)
tableName := DocumentRelate{}.TableName()
updateFields = m.FilterValidFields(tableName, updateFields...)
if len(updateFields) > 0 { // 更新指定字段
db = db.Select(updateFields)
} else { // 更新全部字段,包括零值字段
db = db.Select(m.GetTableFields(tableName))
}
err = db.Where("id = ?", documentRelate.Id).Updates(documentRelate).Error
if err != nil {
m.logger.Error("UpdateDocumentRelate", zap.Error(err))
}
return
}
// GetDocumentRelate 根据id获取DocumentRelate
func (m *DBModel) GetDocumentRelate(id int64, fields ...string) (documentRelate DocumentRelate, err error) {
db := m.db
fields = m.FilterValidFields(DocumentRelate{}.TableName(), fields...)
if len(fields) > 0 {
db = db.Select(fields)
}
err = db.Where("id = ?", id).First(&documentRelate).Error
return
}
type OptionGetDocumentRelateList struct {
Page int
Size int
WithCount bool // 是否返回总数
Ids []interface{} // id列表
SelectFields []string // 查询字段
QueryRange map[string][2]interface{} // map[field][]{min,max}
QueryIn map[string][]interface{} // map[field][]{value1,value2,...}
QueryLike map[string][]interface{} // map[field][]{value1,value2,...}
Sort []string
}
// GetDocumentRelateList 获取DocumentRelate列表
func (m *DBModel) GetDocumentRelateList(opt *OptionGetDocumentRelateList) (documentRelateList []DocumentRelate, total int64, err error) {
tableName := DocumentRelate{}.TableName()
db := m.db.Model(&DocumentRelate{})
db = m.generateQueryRange(db, tableName, opt.QueryRange)
db = m.generateQueryIn(db, tableName, opt.QueryIn)
db = m.generateQueryLike(db, tableName, opt.QueryLike)
if len(opt.Ids) > 0 {
db = db.Where("id in (?)", opt.Ids)
}
if opt.WithCount {
err = db.Count(&total).Error
if err != nil {
m.logger.Error("GetDocumentRelateList", zap.Error(err))
return
}
}
opt.SelectFields = m.FilterValidFields(tableName, opt.SelectFields...)
if len(opt.SelectFields) > 0 {
db = db.Select(opt.SelectFields)
}
db = m.generateQuerySort(db, tableName, opt.Sort)
db = db.Offset((opt.Page - 1) * opt.Size).Limit(opt.Size)
err = db.Find(&documentRelateList).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetDocumentRelateList", zap.Error(err))
}
return
}
// DeleteDocumentRelate 删除数据
func (m *DBModel) DeleteDocumentRelate(ids []interface{}) (err error) {
err = m.db.Where("id in (?)", ids).Delete(&DocumentRelate{}).Error
if err != nil {
m.logger.Error("DeleteDocumentRelate", zap.Error(err))
}
return
}
func (m *DBModel) GetRelatedDocuments(documentId int64) (docs []Document, err error) {
var (
docRelate DocumentRelate
docIds []int64
cfg = m.GetConfigOfSecurity(ConfigSecurityDocumentRelatedDuration)
keywords []interface{}
opt = &OptionGetDocumentList{
WithCount: false,
Page: 1,
Size: 10,
QueryIn: make(map[string][]interface{}),
QueryLike: make(map[string][]interface{}),
SelectFields: []string{
"id", "title", "ext",
},
}
isExpired bool
)
if cfg.DocumentRelatedDuration <= 0 {
return
}
err = m.db.Where("document_id = ?", documentId).First(&docRelate).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetRelatedDocuments", zap.Error(err))
return
}
// 未过期
if docRelate.Id > 0 && docRelate.UpdatedAt.Add(time.Duration(cfg.DocumentRelatedDuration)*time.Hour*24).After(time.Now()) {
json.Unmarshal([]byte(docRelate.RelatedDocumentId), &docIds)
} else {
isExpired = true
}
if len(docIds) == 0 {
doc, _ := m.GetDocument(documentId, "id", "title", "keywords")
if doc.Id > 0 {
for _, kw := range strings.Split(doc.Keywords, ",") {
keywords = append(keywords, strings.TrimSpace(kw))
}
opt.QueryLike["title"] = keywords
opt.QueryLike["keywords"] = keywords
opt.QueryLike["description"] = keywords
}
} else {
opt.QueryIn["id"] = util.Slice2Interface(docIds)
}
docs, _, _ = m.GetDocumentList(opt)
if isExpired && len(docs) > 0 {
for _, doc := range docs {
docIds = append(docIds, doc.Id)
}
bs, _ := json.Marshal(docIds)
docRelate.DocumentId = documentId
docRelate.RelatedDocumentId = string(bs)
if docRelate.Id > 0 {
m.UpdateDocumentRelate(&docRelate)
} else {
m.CreateDocumentRelate(&docRelate)
}
}
return
}

@ -161,6 +161,7 @@ func (m *DBModel) SyncDB() (err error) {
&Document{},
&DocumentCategory{},
&DocumentScore{},
&DocumentRelate{},
&Download{},
&Friendlink{},
&User{},

@ -40,6 +40,14 @@ export const getDocument = (params) => {
})
}
export const getRelatedDocuments = (params) => {
return service({
url: '/api/v1/document/related',
method: 'get',
params,
})
}
export const listDocumentForHome = (params) => {
return service({
url: '/api/v1/document/home',

@ -3,7 +3,10 @@
<ul>
<li v-for="doc in docs" :key="'doc-' + doc.id">
<nuxt-link to="/document/" class="el-link el-link--default">
<img :src="'/static/images/' + doc.type + '_24.png'" alt="" />
<img
:src="'/static/images/' + getIcon(doc.ext) + '_24.png'"
:alt="getIcon(doc.ext) + '文档'"
/>
{{ doc.title }}
</nuxt-link>
</li>
@ -12,6 +15,7 @@
</template>
<script>
import { getIcon } from '~/utils/utils'
export default {
name: 'DocumentSimpleList',
props: {
@ -23,13 +27,10 @@ export default {
data() {
return {}
},
head() {
return {
title: 'MOREDOC · 魔豆文库,开源文库系统',
}
},
async created() {},
methods: {},
methods: {
getIcon,
},
}
</script>
<style lang="scss">

@ -154,9 +154,13 @@
<div slot="header">分享用户</div>
<user-card :hide-actions="true" :user="document.user" />
</el-card>
<el-card shadow="never" class="mgt-20px relate-docs">
<el-card
shadow="never"
class="mgt-20px relate-docs"
v-if="relatedDocuments.length > 0"
>
<div slot="header">相关文档</div>
<document-simple-list :docs="docs" />
<document-simple-list :docs="relatedDocuments" />
</el-card>
</el-col>
</el-row>
@ -256,7 +260,11 @@
<script>
import { mapActions, mapGetters } from 'vuex'
import DocumentSimpleList from '~/components/DocumentSimpleList.vue'
import { getDocument, downloadDocument } from '~/api/document'
import {
getDocument,
downloadDocument,
getRelatedDocuments,
} from '~/api/document'
import { getFavorite, createFavorite, deleteFavorite } from '~/api/favorite'
import { formatDatetime, formatBytes, getIcon } from '~/utils/utils'
import FormComment from '~/components/FormComment.vue'
@ -298,6 +306,7 @@ export default {
document_title: '',
reason: 1,
},
relatedDocuments: [],
}
},
head() {
@ -309,7 +318,11 @@ export default {
...mapGetters('category', ['categoryMap']),
},
created() {
Promise.all([this.getDocument(), this.getFavorite()])
Promise.all([
this.getDocument(),
this.getFavorite(),
this.getRelatedDocuments(),
])
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
@ -446,6 +459,14 @@ export default {
}
this.downloading = false
},
async getRelatedDocuments() {
const res = await getRelatedDocuments({
id: this.documentId,
})
if (res.status === 200) {
this.relatedDocuments = res.data.document || []
}
},
prevPage() {
if (this.currentPage > 1) {
const currentPage = this.currentPage - 1
@ -482,7 +503,7 @@ export default {
zoomOut() {
if (this.scaleSpan > 18) {
const currentPage = this.currentPage
this.scaleSpan -= 3
this.scaleSpan -= 6
this.$nextTick(() => {
this.zoomSetPage(currentPage)
})
@ -492,7 +513,7 @@ export default {
zoomIn() {
if (this.scaleSpan < 24) {
const currentPage = this.currentPage
this.scaleSpan += 3
this.scaleSpan += 6
this.$nextTick(() => {
this.zoomSetPage(currentPage)
})

Loading…
Cancel
Save