feat(业务用户组): AUG26-003 业务用户组与店铺负责人分组导入
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 1h43m42s

- 迁移 000221:新增 tb_business_user_group、tb_business_user_group_member、tb_shop_business_owner_import_task,成员一账号一行由部分唯一索引保证,店铺所属组按当前负责人实时推导,不回填历史分组。
- 用户组 CRUD、成员改组/清空归属、店铺批量交接(原子失败不部分写入)。
- 店铺负责人 CSV 导入任务:逐行独立事务、逐行明细、任务级与行级失败分离。
- 读侧推导与筛选:未分组、业务线、停用组可筛出并带停用标记。
- 补齐操作审计动作与资源、openapi 清单、发布门禁巡检表清单。
- 归档 add-shop-salesperson-groups 变更并同步 openspec/specs/business-user-group,补齐 AUG26-003 验证证据链。
This commit is contained in:
2026-09-14 16:51:44 +08:00
parent 957a235585
commit c7f9e005af
56 changed files with 4166 additions and 344 deletions

40
pkg/utils/encoding.go Normal file
View File

@@ -0,0 +1,40 @@
package utils
import (
"fmt"
"strings"
"unicode/utf8"
"golang.org/x/text/encoding/simplifiedchinese"
)
// utf8BOM 是 UTF-8 字节顺序标记,上传的 CSV 常带该前缀。
var utf8BOM = []byte{0xEF, 0xBB, 0xBF}
// replacementChar 是解码器无法映射字节时产生的替换字符。
const replacementChar = "\uFFFD"
// DecodeTextToUTF8 把上传文本解码为 UTF-8 字节。
// 先剥离 UTF-8 BOM剥离后已是合法 UTF-8 时原样返回,否则按 GBK 回退解码。
// 仅在 UTF-8 校验失败时才尝试 GBK避免对合法 UTF-8 内容做启发式改写;
// GBK 解码失败或仍残留无法映射的字节时返回错误,由调用方按任务级失败处理。
func DecodeTextToUTF8(data []byte) ([]byte, error) {
trimmed := data
if len(trimmed) >= len(utf8BOM) && string(trimmed[:len(utf8BOM)]) == string(utf8BOM) {
trimmed = trimmed[len(utf8BOM):]
}
if utf8.Valid(trimmed) {
return trimmed, nil
}
decoded, err := simplifiedchinese.GBK.NewDecoder().Bytes(trimmed)
if err != nil {
return nil, fmt.Errorf("GBK 解码失败: %w", err)
}
if !utf8.Valid(decoded) {
return nil, fmt.Errorf("GBK 解码结果不是合法 UTF-8")
}
if strings.Contains(string(decoded), replacementChar) {
return nil, fmt.Errorf("GBK 解码存在无法映射的字节")
}
return decoded, nil
}