后端go完成文档分享链接功能

前言

一个使用 Go 实现文档分享链接功能的后端方案,包含生成分享链接、验证权限、访问控制等核心功能。

设计思路

为每个文档生成唯一的分享 token(UUID)

支持设置分享权限(只读 / 编辑)和有效期

通过中间件验证分享链接的有效性

提供创建、查询、撤销分享的接口

实现代码

数据模型定义

go 复制代码
package main

import (
	"time"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

// Document 文档模型
type Document struct {
	ID      uint   `gorm:"primaryKey"`
	Title   string `gorm:"size:255"`
	Content string `gorm:"type:text"`
	OwnerID uint   // 文档所有者ID
}

// Share 分享记录模型
type Share struct {
	ID         uint           `gorm:"primaryKey"`
	ShareID    string         `gorm:"size:36;uniqueIndex"` // 分享链接唯一标识(UUID)
	DocumentID uint           `gorm:"index"`               // 关联的文档ID
	Permission string         `gorm:"size:20"`             // 权限:read / edit
	ExpiresAt  *time.Time     `gorm:"index"`               // 过期时间(nil表示永久有效)
	CreatedAt  time.Time      `gorm:"index"`
	DeletedAt  gorm.DeletedAt `gorm:"index"` // 用于软删除(撤销分享)
}

// 生成新的分享记录
func NewShare(docID uint, permission string, expiresAt *time.Time) *Share {
	return &Share{
		ShareID:    uuid.New().String(),
		DocumentID: docID,
		Permission: permission,
		ExpiresAt:  expiresAt,
	}
}

数据库初始化

go 复制代码
package main

import (
	"gorm.io/driver/sqlite" // 示例使用SQLite,可替换为MySQL等
	"gorm.io/gorm"
)

var db *gorm.DB

// 初始化数据库连接
func initDB() error {
	var err error
	db, err = gorm.Open(sqlite.Open("documents.db"), &gorm.Config{})
	if err != nil {
		return err
	}

	// 迁移数据表
	return db.AutoMigrate(&Document{}, &Share{})
}

业务逻辑

go 复制代码
package main

import (
	"errors"
	"time"
)

// 创建文档分享链接
func CreateShare(docID uint, permission string, expiresInHours int) (*Share, error) {
	// 验证权限类型
	if permission != "read" && permission != "edit" {
		return nil, errors.New("invalid permission (must be 'read' or 'edit')")
	}

	// 计算过期时间(expiresInHours=0表示永久有效)
	var expiresAt *time.Time
	if expiresInHours > 0 {
		t := time.Now().Add(time.Duration(expiresInHours) * time.Hour)
		expiresAt = &t
	}

	// 创建分享记录
	share := NewShare(docID, permission, expiresAt)
	if err := db.Create(share).Error; err != nil {
		return nil, err
	}
	return share, nil
}

// 验证分享链接有效性
func ValidateShare(shareID string) (*Share, error) {
	var share Share
	if err := db.Where("share_id = ?", shareID).First(&share).Error; err != nil {
		return nil, errors.New("invalid or expired share link")
	}

	// 检查是否过期
	if share.ExpiresAt != nil && time.Now().After(*share.ExpiresAt) {
		return nil, errors.New("share link has expired")
	}

	return &share, nil
}

// 撤销分享链接(软删除)
func RevokeShare(shareID string, ownerID uint) error {
	// 验证文档所有权
	var doc Document
	if err := db.Where("id = (SELECT document_id FROM shares WHERE share_id = ?)", shareID).First(&doc).Error; err != nil {
		return errors.New("document not found")
	}
	if doc.OwnerID != ownerID {
		return errors.New("no permission to revoke this share")
	}

	// 软删除分享记录
	return db.Where("share_id = ?", shareID).Delete(&Share{}).Error
}

// 通过分享链接获取文档
func GetDocumentByShare(shareID string) (*Document, *Share, error) {
	share, err := ValidateShare(shareID)
	if err != nil {
		return nil, nil, err
	}

	var doc Document
	if err := db.Where("id = ?", share.DocumentID).First(&doc).Error; err != nil {
		return nil, nil, errors.New("document not found")
	}

	return &doc, share, nil
}

API实现

go 复制代码
package main

import (
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
)

// 1. 创建分享链接
// 请求: POST /api/documents/:docID/share
// 参数: permission (read/edit), expiresInHours (0=永久)
func createShareHandler(c *gin.Context) {
	docID, err := strconv.ParseUint(c.Param("docID"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid document ID"})
		return
	}

	var req struct {
		Permission     string `json:"permission" binding:"required,oneof=read edit"`
		ExpiresInHours int    `json:"expiresInHours" binding:"min=0"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 这里简化处理,实际应从认证中获取用户ID
	ownerID := uint(1) // 示例:假设当前用户ID为1

	// 验证文档所有权(实际项目需检查doc.OwnerID == ownerID)
	var doc Document
	if err := db.First(&doc, docID).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "document not found"})
		return
	}

	share, err := CreateShare(uint(docID), req.Permission, req.ExpiresInHours)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create share"})
		return
	}

	// 生成完整分享链接
	shareURL := "http://your-domain.com/shared/" + share.ShareID
	c.JSON(http.StatusOK, gin.H{
		"shareURL":  shareURL,
		"shareID":   share.ShareID,
		"permission": share.Permission,
		"expiresAt": share.ExpiresAt,
	})
}

// 2. 通过分享链接访问文档
// 请求: GET /api/shared/:shareID
func accessSharedDocumentHandler(c *gin.Context) {
	shareID := c.Param("shareID")
	doc, share, err := GetDocumentByShare(shareID)
	if err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"document": map[string]interface{}{
			"id":      doc.ID,
			"title":   doc.Title,
			"content": doc.Content,
		},
		"permission": share.Permission,
	})
}

// 3. 撤销分享链接
// 请求: DELETE /api/shares/:shareID
func revokeShareHandler(c *gin.Context) {
	shareID := c.Param("shareID")
	ownerID := uint(1) // 实际应从认证中获取

	if err := RevokeShare(shareID, ownerID); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "share revoked successfully"})
}

主程序

go 复制代码
package main

import (
	"github.com/gin-gonic/gin"
)

func main() {
	// 初始化数据库
	if err := initDB(); err != nil {
		panic("failed to initialize database: " + err.Error())
	}

	// 设置路由
	r := gin.Default()
	api := r.Group("/api")
	{
		// 文档分享相关接口
		api.POST("/documents/:docID/share", createShareHandler)       // 创建分享
		api.GET("/shared/:shareID", accessSharedDocumentHandler)      // 访问分享文档
		api.DELETE("/shares/:shareID", revokeShareHandler)            // 撤销分享
	}

	// 启动服务
	r.Run(":8080")
}

3. 核心功能说明

  • 分享链接生成:
    使用 UUID 作为唯一标识,避免猜测
    支持设置权限(只读 / 编辑)和有效期
    生成的链接格式:http://your-domain.com/shared/{shareID}
  • 安全验证:
    验证分享链接是否存在、未过期、未被撤销
    检查访问者权限(只读 / 编辑)
    只有文档所有者可撤销分享
  • 扩展建议:
    增加用户认证(如 JWT),替换示例中的ownerID硬编码
    实现分享记录列表接口,方便用户管理所有分享
    增加访问日志,记录分享链接的访问情况
    对敏感文档可增加密码保护功能
相关推荐
Franciz小测测1 小时前
Python连接RabbitMQ三大方案全解析
开发语言·后端·ruby
海梨花2 小时前
又是秒杀又是高并发,你的接口真的扛得住吗?
java·后端·jmeter
代码雕刻家2 小时前
C语言的左对齐符号-
c语言·开发语言
小肖爱笑不爱笑2 小时前
2025/11/19 网络编程
java·运维·服务器·开发语言·计算机网络
Livingbody2 小时前
win11上wsl本地安装版本ubuntu25.10
后端
郑州光合科技余经理2 小时前
开发指南:海外版外卖跑腿系统源码解析与定制
java·开发语言·mysql·spring cloud·uni-app·php·深度优先
用户8356290780512 小时前
如何在 C# 中自动化生成 PDF 表格
后端·c#
星释3 小时前
Rust 练习册 44:Trait 中的同名函数调用
开发语言·后端·rust
京东零售技术3 小时前
并发丢数据深度剖析:JED的锁机制与事务实战踩坑及解决方案
后端