通过excel控制白名单

我有一个接口现在需要实现以下的需求,我有一个产品列表,需要通过excel文件实现指定客户可以查看还是所有客户可以查看,现在默认是所有客户都可以查看。允许用户上传文件,在用户上传完文件之后就对excel的文件的格式进行校验,文件第一列手机号码,第二列账户id。如果有问题需要报错提示文件格式错误供前端使用,如果没问题就把文件数据拿出来存到数据库里面去,供文件列表接口使用判断某个用户能看到哪些产品列表;还需要把文件存到oss里面去,方便客户下载文件重新上传。项目语言为go语言。我希望使用关联表方案

我来帮你设计这个功能的实现方案。我们可以按以下步骤来实现:

  1. 首先定义必要的数据结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 产品与用户关联表
type ProductUserMapping struct {
ID uint `gorm:"primaryKey"`
ProductID uint `gorm:"index"`
Phone string `gorm:"index"`
AccountID string `gorm:"index"`
CreatedAt time.Time
UpdatedAt time.Time
}

// 上传文件记录表
type ProductAccessFile struct {
ID uint `gorm:"primaryKey"`
FileName string
OssPath string
UploadedBy uint // 上传者ID
CreatedAt time.Time
UpdatedAt time.Time
}

// Excel数据结构
type ProductAccessRecord struct {
Phone string
AccountID string
}
  1. 实现文件上传和处理的 handler:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
func HandleProductAccessUpload(c *gin.Context) {
// 获取上传的文件
file, header, err := c.Request.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "文件上传失败"})
return
}
defer file.Close()

// 检查文件扩展名
if !strings.HasSuffix(strings.ToLower(header.Filename), ".xlsx") {
c.JSON(http.StatusBadRequest, gin.H{"error": "只支持 Excel 文件格式"})
return
}

// 读取 Excel 文件
records, err := readExcelFile(file)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

// 上传文件到 OSS
ossPath, err := uploadToOSS(file, header.Filename)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "文件保存失败"})
return
}

// 开启事务
tx := db.Begin()

// 保存文件记录
fileRecord := ProductAccessFile{
FileName: header.Filename,
OssPath: ossPath,
UploadedBy: getCurrentUserID(c),
}
if err := tx.Create(&fileRecord).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存文件记录失败"})
return
}

// 保存映射关系
for _, record := range records {
mapping := ProductUserMapping{
Phone: record.Phone,
AccountID: record.AccountID,
}
if err := tx.Create(&mapping).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存访问权限失败"})
return
}
}

// 提交事务
tx.Commit()

c.JSON(http.StatusOKmessage": "上传成功"})
}
  1. 实现 Excel 文件读取和验证:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func readExcelFile(file multipart.File) ([]ProductAccessRecord, error) {
xlsx, err := excelize.OpenReader(file)
if err != nil {
return nil, errors.New("无法读取 Excel 文件")
}

sheets := xlsx.GetSheetList()
if len(sheets) == 0 {
return nil, errors.New("Excel 文件没有工作表")
}

rows, err := xlsx.GetRows(sheets[0])
if err != nil {
return nil, errors.New("无法读取工作表内容")
}

var records []ProductAccessRecord
for i, row := range rows {
// 跳过标题行
if i == 0 {
continue
}

// 检查行数据完整性
if len(row) < 2 {
return nil, fmt.Errorf("第 %d 行数据不完整", i+1)
}

// 验证手机号格式
if !validatePhone(row[0]) {
return nil, fmt.Errorf("第 %d 行手机号格式错误", i+1)
}

records = append(records, ProductAccessRecord{
Phone: row[0],
AccountID: row[1],
})
}

return records, nil
}

func validatePhone(phone string) bool {
reg := regexp.MustCompile(`^1[3-9]\d{9}$`)
return reg.MatchString(phone)
}
  1. 实现产品列表接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func GetProductList(c *gin.Context) {
var products []Product
phone := getCurrentUserPhone(c)
accountID := getCurrentUserAccountID(c)

// 查询用户是否有特定访问权限
var mapping ProductUserMapping
result := db.Where("phone = ? OR account_id = ?", phone, accountID).First(&mapping)

if result.Error == gorm.ErrRecordNotFound {
// 如果没有特定权限,返回所有产品
db.Find(&products)
} else {
// 如果有特定权限,返回指定产品
db.Joins("JOIN product_user_mappings ON products.id = product_user_mappings.product_id").
Where("product_user_mappings.phone = ? OR product_user_mappings.account_id = ?", phone, accountID).
Find(&products)
}

c.JSON(http.StatusOK, products)
}
  1. 实现文件下载接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func DownloadAccessFile(c *gin.Context) {
fileID := c.Param("id")

var file ProductAccessFile
if err := db.First(&file, fileID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "文件不存在"})
return
}

// 从 OSS 获取文件
ossClient := getOSSClient()
fileURL, err := ossClient.SignURL(file.OssPath, http.MethodGet, 3600)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取下载链接失败"})
return
}

c.JSON(http.StatusOK, gin.H{"download_url": fileURL})
}

这个方案的主要特点:

  1. 使用关联表 ProductUserMapping 存储产品访问权限
  2. 支持通过手机号或账户 ID 进行权限控制
  3. 保存上传的文件到 OSS,并记录文件信息
  4. 使用事务确保数据一致性
  5. 提供完整的文件格式验证
  6. 支持文件下载功能

你需要注意以下几点:

  1. 需要安装必要的依赖:

    1
    2
    3
    go get github.com/xuri/excelize/v2
    go get gorm.io/gorm
    go get github.com/gin-gonic/gin
  2. 需要配置 OSS 客户端

  3. 需要配置数据库连接

  4. 可能需要添加更多的错误处理和日志记录

  5. 可以添加更多的文件格式验证规则

  6. 可以添加并发处理来提高性能

你觉得这个方案怎么样?如果有任何需要调整或补充的地方,请告诉我。

作者

JIeJaitt

发布于

2025-01-24

更新于

2025-01-24

许可协议

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×