Files
yunzerwebsiteallinone/go/models/oa_document.go
T
2026-09-15 10:44:31 +08:00

287 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package models
import (
"strings"
"sync"
"time"
"github.com/beego/beego/v2/client/orm"
)
// OA 文档管理(文档库 / 文档图谱)数据模型。
//
// 三张表按租户 tid 隔离:
// - yz_backend_oa_doc_category 文档分类(树形,parent_id=0 为一级)
// - yz_backend_oa_doc 文档主体(附件信息冗余存储,避免每次联查文件表)
// - yz_backend_oa_doc_link 文档关联关系(引用/相关/版本/从属),文档图谱的连线来源
// OaDocCategory OA文档分类: yz_backend_oa_doc_category
//
// 双空间模型:文档库按「共享文档 / 私密文档」两个空间组织,每个空间各自有一棵分类树。
// - 共享文档空间(Scope="shared"、UserID=0):租户级,租户内所有成员共用同一套文件夹结构;
// - 私密文档空间(Scope="personal"、UserID=登录用户的 yz_system_tenant_user.uid):
// 绑定到租户的用户级别,每位用户拥有自己独立的一套文件夹,互不干扰;
// - 文档的可见性(OaDoc.Visibility)与所在空间一一对应:共享空间→租户公开(0),私密空间→私密(1);
// - 系统内置的「项目文档」属于共享空间(Scope="shared"、UserID=0);
// - 「未分类」是虚拟分类(文档 category_id=0),不落库、不可删除,两个空间各自独立计算。
type OaDocCategory struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Tid int `orm:"column(tid)" json:"tid"`
// UserID 分类归属用户:共享空间恒为 0(租户级);私密空间为创建者的 yz_system_tenant_user.uid。
UserID uint64 `orm:"column(user_id);default(0)" json:"user_id"`
ParentID uint64 `orm:"column(parent_id);default(0)" json:"parent_id"`
Name string `orm:"column(name);size(128)" json:"name"`
Sort int `orm:"column(sort);default(0)" json:"sort"`
Remark string `orm:"column(remark);size(255);default()" json:"remark"`
IsSystem int8 `orm:"column(is_system);default(0)" json:"is_system"`
// Scope 所属空间:shared=共享文档空间,personal=私密文档空间(绑定到用户级别)。
Scope string `orm:"column(scope);size(16);default(shared)" json:"scope"`
IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *OaDocCategory) TableName() string {
return "yz_backend_oa_doc_category"
}
// OaDoc OA文档: yz_backend_oa_doc
// status: 0-草稿 1-已发布 2-已归档
// doc_type: 0-其他 1-文档 2-表格 3-演示 4-PDF 5-图片 6-压缩包 7-视频 8-音频
type OaDoc struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Tid int `orm:"column(tid)" json:"tid"`
CategoryID uint64 `orm:"column(category_id);default(0)" json:"category_id"`
Title string `orm:"column(title);size(255)" json:"title"`
FileID uint64 `orm:"column(file_id);default(0)" json:"file_id"`
FileURL string `orm:"column(file_url);size(512);default()" json:"file_url"`
FileName string `orm:"column(file_name);size(255);default()" json:"file_name"`
Ext string `orm:"column(ext);size(16);default()" json:"ext"`
Size uint64 `orm:"column(size);default(0)" json:"size"`
DocType int8 `orm:"column(doc_type);default(0)" json:"doc_type"`
Tags string `orm:"column(tags);size(255);default()" json:"tags"`
Summary string `orm:"column(summary);size(1000);default()" json:"summary"`
// Content 在线制作文档的编辑源(富文本 HTML),仅用于再次编辑;上传文件或普通文档为空。
// 注意:文档对外的写入/下载格式为 docx(由该源导出),此字段只是编辑器的中间态。
Content string `orm:"column(content);type(longtext);null" json:"content,omitempty"`
// IsOnline 是否在线制作(非持久化字段,列表接口按 Content 是否为空计算)
IsOnline int8 `orm:"-" json:"is_online"`
Status int8 `orm:"column(status);default(0)" json:"status"`
Version int `orm:"column(version);default(1)" json:"version"`
IsStar int8 `orm:"column(is_star);default(0)" json:"is_star"`
OwnerID uint64 `orm:"column(owner_id);default(0)" json:"owner_id"`
OwnerName string `orm:"column(owner_name);size(100);default()" json:"owner_name"`
ViewCount int `orm:"column(view_count);default(0)" json:"view_count"`
DownCount int `orm:"column(download_count);default(0)" json:"download_count"`
CreatorID uint64 `orm:"column(creator_id);default(0)" json:"creator_id"`
CreatorName string `orm:"column(creator_name);size(100);default()" json:"creator_name"`
// Visibility 可见性:0-租户公开(默认,租户内所有后台用户可见)1-私密(仅创建者与被共享者可见)
Visibility int8 `orm:"column(visibility);default(0)" json:"visibility"`
IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *OaDoc) TableName() string {
return "yz_backend_oa_doc"
}
// OaDocLink OA文档关联: yz_backend_oa_doc_link
// relation: reference-引用 related-相关 version-版本迭代 belong-从属
type OaDocLink struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Tid int `orm:"column(tid)" json:"tid"`
SourceID uint64 `orm:"column(source_id);default(0)" json:"source_id"`
TargetID uint64 `orm:"column(target_id);default(0)" json:"target_id"`
Relation string `orm:"column(relation);size(32);default(related)" json:"relation"`
Remark string `orm:"column(remark);size(255);default()" json:"remark"`
CreatorID uint64 `orm:"column(creator_id);default(0)" json:"creator_id"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
}
func (m *OaDocLink) TableName() string {
return "yz_backend_oa_doc_link"
}
// OaDocShare OA文档共享: yz_backend_oa_doc_share
// 私密文档(visibility=1)在"创建者可见"之外,额外授权给指定用户或部门。
// share_type: 0-用户(target_id 为 yz_system_tenant_user.uid)
//
// 1-部门(target_id 为 yz_backend_organization.id,含其全部子部门)
type OaDocShare struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Tid int `orm:"column(tid)" json:"tid"`
DocID uint64 `orm:"column(doc_id);default(0)" json:"doc_id"`
ShareType int8 `orm:"column(share_type);default(0)" json:"share_type"`
TargetID uint64 `orm:"column(target_id);default(0)" json:"target_id"`
CreatorID uint64 `orm:"column(creator_id);default(0)" json:"creator_id"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
}
func (m *OaDocShare) TableName() string {
return "yz_backend_oa_doc_share"
}
// 文档分类内置标记常量(is_system)
const (
DocCategoryNormal int8 = 0 // 普通分类,租户可自行删除
DocCategorySystem int8 = 1 // 系统内置分类,任何租户都不可删除
)
// 文档范围(scope):文档库按「共享文档 / 私密文档」两个空间组织,分类同样按此隔离。
// 共享空间(shared):租户级,所有人共用;私密空间(personal):绑定到用户级别,每人独立一套。
// 文档侧复用 visibility 字段:personal 对应私密(1),shared 对应租户公开(0)。
const (
DocScopeAll = "all"
DocScopePersonal = "personal"
DocScopeShared = "shared"
)
// DocCategoryNameProject 系统内置分类名称:项目文档,只属于共享文档范围。
// 每个租户首次访问文档接口时自动补建自己那一份(按 tid 隔离),租户之间互不可见。
const DocCategoryNameProject = "项目文档"
// docCategorySystemRemark 内置分类的默认备注
const docCategorySystemRemark = "系统内置分类,不可删除"
// 文档类型常量(doc_type)
const (
DocTypeOther int8 = 0
DocTypeDoc int8 = 1 // doc / docx / txt / md / rtf
DocTypeSheet int8 = 2 // xls / xlsx / csv
DocTypeSlide int8 = 3 // ppt / pptx
DocTypePDF int8 = 4 // pdf
DocTypeImage int8 = 5 // jpg / png / gif / bmp / webp
DocTypeZip int8 = 6 // zip / rar / 7z / tar / gz
DocTypeVideo int8 = 7
DocTypeAudio int8 = 8
)
// 文档状态常量(status)
const (
DocStatusDraft int8 = 0
DocStatusPublish int8 = 1
DocStatusArchive int8 = 2
)
// 文档关联关系常量(relation)
const (
DocRelationReference = "reference"
DocRelationRelated = "related"
DocRelationVersion = "version"
DocRelationBelong = "belong"
)
// 文档可见性常量(visibility)
const (
DocVisibilityPublic int8 = 0 // 租户公开
DocVisibilityPrivate int8 = 1 // 私密
)
// 文档共享目标类型常量(share_type)
const (
DocShareTypeUser int8 = 0 // 共享给指定用户(target_id = yz_system_tenant_user.uid)
DocShareTypeOrg int8 = 1 // 共享给部门(target_id = yz_backend_organization.id,含子部门)
)
// extDocTypeMap 扩展名 -> 文档类型
var extDocTypeMap = map[string]int8{
"doc": DocTypeDoc, "docx": DocTypeDoc, "txt": DocTypeDoc, "md": DocTypeDoc,
"rtf": DocTypeDoc, "wps": DocTypeDoc, "odt": DocTypeDoc,
"xls": DocTypeSheet, "xlsx": DocTypeSheet, "csv": DocTypeSheet, "ods": DocTypeSheet,
"ppt": DocTypeSlide, "pptx": DocTypeSlide, "odp": DocTypeSlide,
"pdf": DocTypePDF,
"jpg": DocTypeImage, "jpeg": DocTypeImage, "png": DocTypeImage,
"gif": DocTypeImage, "bmp": DocTypeImage, "webp": DocTypeImage, "svg": DocTypeImage,
"zip": DocTypeZip, "rar": DocTypeZip, "7z": DocTypeZip, "tar": DocTypeZip, "gz": DocTypeZip,
"mp4": DocTypeVideo, "webm": DocTypeVideo, "mov": DocTypeVideo, "avi": DocTypeVideo,
"mp3": DocTypeAudio, "wav": DocTypeAudio, "ogg": DocTypeAudio,
}
// DocTypeByExt 根据扩展名推断文档类型,未知返回"其他"。
func DocTypeByExt(ext string) int8 {
if t, ok := extDocTypeMap[strings.ToLower(strings.TrimPrefix(strings.TrimSpace(ext), "."))]; ok {
return t
}
return DocTypeOther
}
// oaDocDefaultCategoryOnce 记录已完成内置分类补建的租户,避免每次请求重复查库。
// key 为 tid(int),value 为 struct{}{};补建失败时不写缓存,下次请求继续重试。
var oaDocDefaultCategoryOnce sync.Map
// EnsureOaDocDefaultCategories 保证指定租户的系统内置分类齐全(当前为「项目文档」)。
// 【租户级初始化】新租户首次使用时补建默认业务数据(非表结构/全局预置数据;结构变更须走 docs/sql/ SQL 文件)。
//
// 租户隔离:内置分类是真实数据行,每个租户各自持有一份(tid 不同),
// 首次访问文档接口时自动补建,租户之间互不可见、互不影响。
//
// 范围隔离:「项目文档」属于共享文档空间(scope=shared、user_id=0),租户内所有人共用。
// 私密文档空间不预置内置分类,由用户自行建立自己的文件夹。
//
// 「未分类」不落库:它是虚拟分类,文档 category_id = 0 即表示未分类,因此天然不可删除;
// 同时在 services 层把「未分类」「项目文档」列为保留名,禁止新建同名分类与之混淆。
//
// 任何失败均静默忽略,不阻断主流程(与其它租户级初始化保持一致)。
func EnsureOaDocDefaultCategories(tid int) {
if Orm == nil || tid <= 0 {
return
}
if _, done := oaDocDefaultCategoryOnce.Load(tid); done {
return
}
if err := ensureOaDocProjectCategory(tid); err == nil {
oaDocDefaultCategoryOnce.Store(tid, struct{}{})
}
}
// ensureOaDocProjectCategory 补建租户的「项目文档」内置分类:
// 1. 已有任意内置分类(is_system=1):说明租户已初始化过(可能被改名),直接返回;
// 2. 已有同名普通分类:升级为内置分类,避免出现两个「项目文档」;
// 3. 都没有:插入一条一级内置分类。
func ensureOaDocProjectCategory(tid int) error {
// 「项目文档」为统一租户分类树下的内置分类(scope=shared)
qs := Orm.QueryTable(new(OaDocCategory)).
Filter("tid", tid).
Filter("scope", DocScopeShared)
sysCount, err := qs.Filter("is_system", DocCategorySystem).Count()
if err != nil {
return err
}
if sysCount > 0 {
return nil
}
var existed []OaDocCategory
if _, err := qs.Filter("name", DocCategoryNameProject).
Filter("is_deleted", 0).
OrderBy("id").Limit(1).All(&existed); err != nil && err != orm.ErrNoRows {
return err
}
if len(existed) > 0 {
item := existed[0]
item.IsSystem = DocCategorySystem
if item.Remark == "" {
item.Remark = docCategorySystemRemark
}
_, err := Orm.Update(&item, "IsSystem", "Remark")
return err
}
_, err = Orm.Insert(&OaDocCategory{
Tid: tid,
ParentID: 0,
Name: DocCategoryNameProject,
Sort: 0,
Remark: docCategorySystemRemark,
IsSystem: DocCategorySystem,
Scope: DocScopeShared,
IsDeleted: 0,
})
return err
}