修复生产商地址受控展示

在平台管理列表、详情和编辑场景中恢复生产商自身企业地址,同时保留全局敏感字段保护。未填写地址时统一显示为‘未填写’,并补充单元测试、操作日志和项目文档。
This commit is contained in:
czl231
2026-08-13 01:37:44 +08:00
parent e5895d95d7
commit 6b5f2cb103
5 changed files with 212 additions and 3 deletions

View File

@@ -37,8 +37,65 @@ type producerAccountUpdateRequest struct {
Remark string `json:"remark"`
}
func ListProducerAccount(ctx *gin.Context) { common.ListResource(ctx, &models.ProducerAccount{}) }
func GetProducerAccount(ctx *gin.Context) { common.GetResource(ctx, &models.ProducerAccount{}) }
// ListProducerAccount 查询生产商分页列表,并向已鉴权的平台管理端恢复企业地址。
func ListProducerAccount(ctx *gin.Context) {
page, size := common.PageSize(ctx)
var list []models.ProducerAccount
var total int64
query := common.ApplyKeywordFilter(ctx, common.ActiveRecords(impl.DBService.Model(&models.ProducerAccount{})), &models.ProducerAccount{})
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
if err := query.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
response, err := common.PublicResourceResponse(list)
if err != nil {
infra.Response.Error(ctx, err)
return
}
// 先执行全局敏感字段保护,再仅为本管理资源恢复生产商自身地址。
protected := common.ProtectPreciseLocation(ctx, &models.ProducerAccount{}, response)
infra.Response.Success(ctx, gin.H{"total": total, "list": restoreProducerAccountAddresses(protected, list)})
}
// GetProducerAccount 查询单个生产商,并返回详情及编辑表单所需的企业地址。
func GetProducerAccount(ctx *gin.Context) {
var producer models.ProducerAccount
if err := common.ActiveRecords(impl.DBService).Where("identity = ?", ctx.Param("identity")).First(&producer).Error; err != nil {
common.RespondRecordError(ctx, err)
return
}
response, err := common.PublicResourceResponse(producer)
if err != nil {
infra.Response.Error(ctx, err)
return
}
protected := common.ProtectPreciseLocation(ctx, &models.ProducerAccount{}, response)
infra.Response.Success(ctx, restoreProducerAccountAddresses(protected, []models.ProducerAccount{producer}))
}
// restoreProducerAccountAddresses 将已鉴权生产商记录的企业地址恢复到安全响应中。
func restoreProducerAccountAddresses(response any, producers []models.ProducerAccount) any {
switch data := response.(type) {
case []any:
for index, item := range data {
if index >= len(producers) {
break
}
if record, ok := item.(map[string]any); ok {
record["address"] = producers[index].Address
}
}
case map[string]any:
if len(producers) > 0 {
data["address"] = producers[0].Address
}
}
return response
}
func CreateProducerAccount(ctx *gin.Context) {
var request producerAccountCreateRequest

View File

@@ -0,0 +1,50 @@
package product
// 功能描述:验证生产商管理响应仅恢复生产商自身的企业地址。
// 版本v1.0
import (
"testing"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
)
// TestRestoreProducerAccountAddresses 验证生产商列表按原顺序恢复企业地址。
func TestRestoreProducerAccountAddresses(t *testing.T) {
response := []any{
map[string]any{"identity": "producer-1"},
map[string]any{"identity": "producer-2"},
}
producers := []models.ProducerAccount{
{Address: "上海市浦东新区生产路 1 号"},
{Address: "上海市闵行区生产路 2 号"},
}
restored := restoreProducerAccountAddresses(response, producers).([]any)
for index, producer := range producers {
if restored[index].(map[string]any)["address"] != producer.Address {
t.Fatalf("第 %d 条生产商地址未正确恢复:%#v", index+1, restored[index])
}
}
}
// TestRestoreProducerAccountAddress 验证生产商详情恢复编辑所需的企业地址。
func TestRestoreProducerAccountAddress(t *testing.T) {
response := map[string]any{"identity": "producer-1"}
producer := models.ProducerAccount{Address: "上海市徐汇区生产路 18 号"}
restored := restoreProducerAccountAddresses(response, []models.ProducerAccount{producer}).(map[string]any)
if restored["address"] != producer.Address {
t.Fatalf("生产商详情地址未正确恢复:%#v", restored)
}
}
// TestRestoreProducerAccountEmptyAddress 验证未填写地址时保留空值,由前端显示“未填写”。
func TestRestoreProducerAccountEmptyAddress(t *testing.T) {
response := map[string]any{"identity": "producer-1"}
restored := restoreProducerAccountAddresses(response, []models.ProducerAccount{{}}).(map[string]any)
if restored["address"] != "" {
t.Fatalf("未填写地址应保留空值:%#v", restored)
}
}

View File

@@ -0,0 +1,46 @@
# 生产商地址受控展示操作日志
操作时间2026-08-13 01:34:48
操作类型:修改
影响模块:平台总后台生产商管理、平台管理 API
## 操作前状态
生产商新建和编辑表单会将地址保存到 `producer_account.address`,但生产商列表与详情复用通用资源查询。通用敏感字段保护会删除响应中的 `address`,导致列表统一显示 `-`,编辑页面也无法取得已保存地址。
## 具体操作
1. 将生产商列表与详情改为专用查询处理器。
2. 保留通用敏感字段保护,并在保护完成后仅对生产商管理资源恢复本记录的企业地址。
3. 将生产商地址空值文案配置为“未填写”,地址仍保持可选。
4. 新增列表、详情和空地址三个单元测试。
## 操作后状态
- 已填写地址:生产商列表、详情和编辑页面可取得并展示企业地址。
- 未填写地址:接口保留空字符串,管理页面显示“未填写”。
- 其他资源和公共响应:继续执行原有全局敏感字段保护,不放宽地址保护规则。
## 代码变更
- `backend/api/internal/logic/platform/product/producer.go`
- 修改 `ListProducerAccount`
- 修改 `GetProducerAccount`
- 新增 `restoreProducerAccountAddresses`
- `backend/api/internal/logic/platform/product/producer_test.go`
- 新增生产商地址恢复及空值边界测试。
- `frontend/platform_admin/src/api/resources.ts`
- 为生产商地址配置“未填写”空值文案。
## 验证结果
- `go test ./internal/logic/platform/product`:通过。
- `npm.cmd run build`通过Vue 类型检查与 Vite 生产构建成功。
- `git diff --check`:通过。
## 风险评估
- 影响范围仅限已鉴权的平台生产商管理接口。
- 没有数据库结构、公共接口或写入规则变更,兼容已有生产商数据。
- 地址恢复依赖查询结果与响应数组顺序一致;单元测试已覆盖多记录顺序映射。

View File

@@ -0,0 +1,56 @@
# 项目文档:生产商地址受控展示 v1.0
## 1. 项目概述
本次修改修复平台总后台生产商管理无法显示已保存企业地址的问题。系统继续使用生产商自身的 `producer_account.address` 字段,不关联用户地址模块,也不改变地址可选规则。
主要技术栈Go、Gin、GORM、Vue 3、TypeScript、Vite。运行环境沿用现有平台 API 与平台总后台环境。
## 2. 目录结构说明
```text
platforms/
├── backend/api/internal/logic/platform/product/
│ ├── producer.go # 生产商管理接口及地址受控恢复逻辑
│ └── producer_test.go # 生产商地址响应测试
├── frontend/platform_admin/src/api/
│ └── resources.ts # 生产商字段与空值文案配置
└── docs/
├── 项目文档_生产商地址受控展示_v1.0.md
└── 操作日志_生产商地址受控展示_20260813.md
```
## 3. 核心文件说明
### `producer.go`
- `ListProducerAccount`:分页查询生产商,执行通用响应保护后恢复每条生产商自身地址。
- `GetProducerAccount`:查询单个生产商,执行通用响应保护后恢复详情及编辑所需地址。
- `restoreProducerAccountAddresses`:按照查询结果顺序,将地址写回列表或详情响应。
该实现依赖 `common.PublicResourceResponse` 完成公共响应投影,并依赖 `common.ProtectPreciseLocation` 保留现有敏感字段保护边界。
### `producer_test.go`
覆盖列表多记录地址映射、单条详情地址恢复以及空地址保留三个场景。
### `resources.ts`
生产商地址字段配置 `emptyText: '未填写'`。空字符串仍由通用字段展示组件处理,不引入生产商专用页面分支。
## 4. 变更记录
- 修复生产商地址已保存但列表显示 `-` 的问题。
- 支持详情和编辑页面取得已保存企业地址。
- 未填写地址时显示“未填写”。
- 未新增依赖、数据库迁移或公共接口。
已知问题:无。
## 5. 维护指南
- 若后续新增其他需要在管理端受控展示的地址资源,应在对应专用处理器中先执行通用保护,再按资源恢复;不要从全局敏感字段集合中删除 `address`
- 修改恢复逻辑后运行:`go test ./internal/logic/platform/product`
- 修改平台资源字段后运行:`npm.cmd run build`
- 地址仍为可选字段;如需改为必填,应单独同步后端校验、前端配置、历史数据治理和接口文档。

View File

@@ -432,7 +432,7 @@ export const resources: ResourceUiDefinition[] = [
}),
]),
define('producer_account', '生产商管理', 'writable', [f('producer_code', { required: true }), f('name', { required: true }), f('credit_code'), f('principal'), f('phone'), f('address'), f('username', { required: true }), f('password', { required: true }), f('display_name'), f('role_code'), f('remark')]),
define('producer_account', '生产商管理', 'writable', [f('producer_code', { required: true }), f('name', { required: true }), f('credit_code'), f('principal'), f('phone'), f('address', { emptyText: '未填写', placeholder: '请输入地址' }), f('username', { required: true }), f('password', { required: true }), f('display_name'), f('role_code'), f('remark')]),
define('product_type', '智能气阀类型', 'editable', [f('code', { required: true }), f('name', { required: true })]),
define('product_warehouse', '智能气阀库房', 'editable', [f('code', { required: true }), f('name', { required: true }), f('address'), f('manager'), f('phone')]),
define('product_info', '智能气阀', 'editable', [f('code', { required: true }), f('name', { required: true }), relation('producer_account_identity', '/producer_account', true), relation('product_type_identity', '/product_type', true), f('params', { required: true }), relation('warehouse_identity', '/product_warehouse'), relation('gas_basic_identity', '/gas_basic'), relation('delivery_basic_identity', '/delivery_basic'), relation('user_account_identity', '/user_account'), f('produced_at', { required: true })], 'list', [