固定生产商管理员角色

新建生产商时由服务端固定写入 admin,编辑时忽略并禁止修改角色。平台页面将 admin 只读显示为‘生产商管理员’,历史未知角色保持原值,不修改数据库或历史数据。补充后端回归测试、操作日志和项目文档。
This commit is contained in:
czl231
2026-08-13 02:07:55 +08:00
parent 6b5f2cb103
commit 27733b313e
6 changed files with 155 additions and 11 deletions

View File

@@ -11,6 +11,9 @@ import (
"github.com/gin-gonic/gin"
)
// producerAdminRoleCode 是生产商账户当前唯一支持的角色编码。
const producerAdminRoleCode = "admin"
type producerAccountCreateRequest struct {
ProducerCode string `json:"producer_code" binding:"required,max=64"`
Name string `json:"name" binding:"required,max=128"`
@@ -21,7 +24,7 @@ type producerAccountCreateRequest struct {
Username string `json:"username" binding:"required,max=64"`
Password string `json:"password" binding:"required"`
DisplayName string `json:"display_name" binding:"max=64"`
RoleCode string `json:"role_code" binding:"max=64"`
RoleCode string `json:"role_code" binding:"max=64"` // 兼容旧客户端,服务端固定使用 admin。
Remark string `json:"remark"`
}
@@ -32,7 +35,7 @@ type producerAccountUpdateRequest struct {
Phone string `json:"phone" binding:"max=32"`
Address string `json:"address" binding:"max=255"`
DisplayName string `json:"display_name" binding:"max=64"`
RoleCode string `json:"role_code" binding:"max=64"`
RoleCode string `json:"role_code" binding:"max=64"` // 兼容旧客户端,更新时不采纳。
Password string `json:"password"`
Remark string `json:"remark"`
}
@@ -97,6 +100,37 @@ func restoreProducerAccountAddresses(response any, producers []models.ProducerAc
return response
}
// newProducerAccount 构造平台创建的生产商账户,并固定为生产商管理员角色。
func newProducerAccount(request producerAccountCreateRequest, passwordHash string) models.ProducerAccount {
return models.ProducerAccount{
Entity: common.NewEntity(common.StatusEnable),
ProducerCode: strings.TrimSpace(request.ProducerCode),
Name: strings.TrimSpace(request.Name),
CreditCode: strings.TrimSpace(request.CreditCode),
Principal: strings.TrimSpace(request.Principal),
Phone: strings.TrimSpace(request.Phone),
Address: strings.TrimSpace(request.Address),
Username: strings.TrimSpace(request.Username),
DisplayName: strings.TrimSpace(request.DisplayName),
PasswordHash: passwordHash,
RoleCode: producerAdminRoleCode,
Remark: request.Remark,
}
}
// producerAccountUpdateValues 生成生产商更新白名单值,刻意忽略客户端角色编码。
func producerAccountUpdateValues(request producerAccountUpdateRequest) gin.H {
return gin.H{
"name": strings.TrimSpace(request.Name),
"credit_code": strings.TrimSpace(request.CreditCode),
"principal": strings.TrimSpace(request.Principal),
"phone": strings.TrimSpace(request.Phone),
"address": strings.TrimSpace(request.Address),
"display_name": strings.TrimSpace(request.DisplayName),
"remark": request.Remark,
}
}
func CreateProducerAccount(ctx *gin.Context) {
var request producerAccountCreateRequest
if ctx.ShouldBindJSON(&request) != nil || !common.IsValidAccountPassword(request.Password) {
@@ -108,11 +142,7 @@ func CreateProducerAccount(ctx *gin.Context) {
infra.Response.Error(ctx, err)
return
}
roleCode := strings.TrimSpace(request.RoleCode)
if roleCode == "" {
roleCode = "admin"
}
data := models.ProducerAccount{Entity: common.NewEntity(common.StatusEnable), ProducerCode: strings.TrimSpace(request.ProducerCode), Name: strings.TrimSpace(request.Name), CreditCode: strings.TrimSpace(request.CreditCode), Principal: strings.TrimSpace(request.Principal), Phone: strings.TrimSpace(request.Phone), Address: strings.TrimSpace(request.Address), Username: strings.TrimSpace(request.Username), DisplayName: strings.TrimSpace(request.DisplayName), PasswordHash: hash, RoleCode: roleCode, Remark: request.Remark}
data := newProducerAccount(request, hash)
if err := impl.DBService.Create(&data).Error; err != nil {
infra.Response.Error(ctx, err)
return
@@ -126,7 +156,7 @@ func UpdateProducerAccount(ctx *gin.Context) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
values := gin.H{"name": strings.TrimSpace(request.Name), "credit_code": strings.TrimSpace(request.CreditCode), "principal": strings.TrimSpace(request.Principal), "phone": strings.TrimSpace(request.Phone), "address": strings.TrimSpace(request.Address), "display_name": strings.TrimSpace(request.DisplayName), "role_code": strings.TrimSpace(request.RoleCode), "remark": request.Remark}
values := producerAccountUpdateValues(request)
if request.Password != "" {
if !common.IsValidAccountPassword(request.Password) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
@@ -139,7 +169,7 @@ func UpdateProducerAccount(ctx *gin.Context) {
}
values["password_hash"] = hash
}
common.UpdateAllowedByIdentity(ctx, &models.ProducerAccount{}, values, []string{"name", "credit_code", "principal", "phone", "address", "display_name", "role_code", "remark", "password_hash"})
common.UpdateAllowedByIdentity(ctx, &models.ProducerAccount{}, values, []string{"name", "credit_code", "principal", "phone", "address", "display_name", "remark", "password_hash"})
}
func DeleteProducerAccount(ctx *gin.Context) {

View File

@@ -9,6 +9,27 @@ import (
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
)
// TestNewProducerAccountUsesAdminRole 验证客户端角色不会改变新生产商的管理员权限。
func TestNewProducerAccountUsesAdminRole(t *testing.T) {
producer := newProducerAccount(producerAccountCreateRequest{
ProducerCode: " producer-1 ",
Name: " 示例生产商 ",
Username: " producer-admin ",
RoleCode: "1",
}, "password-hash")
if producer.RoleCode != producerAdminRoleCode {
t.Fatalf("生产商角色编码必须为 %q实际为 %q", producerAdminRoleCode, producer.RoleCode)
}
}
// TestProducerAccountUpdateIgnoresRole 验证编辑生产商时不会写入客户端角色编码。
func TestProducerAccountUpdateIgnoresRole(t *testing.T) {
values := producerAccountUpdateValues(producerAccountUpdateRequest{Name: "生产商", RoleCode: "1"})
if _, exists := values["role_code"]; exists {
t.Fatalf("生产商更新值不应包含角色编码:%#v", values)
}
}
// TestRestoreProducerAccountAddresses 验证生产商列表按原顺序恢复企业地址。
func TestRestoreProducerAccountAddresses(t *testing.T) {
response := []any{