6-18 代码优化
本文档说明 2026-06-18 对 GET /api/app/label-template 列表接口的第二轮修复。
6-17 已完成 scope 库结构兼容(未迁移 fl_label_template_partner / fl_label_template_region 时仍可查询),但测试环境 Web Label Templates 页仍返回 500,Network 中:
GET /api/app/label-template?SkipCount=1&MaxResultCount=10
测试环境:http://flus-test.3ffoodsafety.com
一、现象
- 页面提示:Failed to load label templates. Request failed.
- 接口 HTTP 500,响应体
errors为空,无具体异常文案。 - 同页
partner/group/location下拉接口可正常加载。
SkipCount=1表示第 1 页(页码从 1 起),不是报错原因。详见Helpers/PagedQueryConvention.cs。
二、根因
服务端日志已明确报错:
Unknown column 'AppliedPartnerType' in 'field list'
对应 SQL:
SELECT ... `AppliedLocationType`,`AppliedPartnerType`,`AppliedRegionType`, ...
FROM `fl_label_template`
WHERE NOT ( `IsDeleted`=1 )
ORDER BY IFNULL(`LastModificationTime`,`CreationTime`) DESC
LIMIT 0,10
2.1 主因:ORM 实体仍映射不存在列
6-17 曾在 FlLabelTemplateDbEntity 上对 AppliedPartnerType / AppliedRegionType 标记 [SugarColumn(IsIgnore = true)],但当前 SqlSugar 运行时仍会把这两列拼进 SELECT(与 fl_label.AppliedRegionType 的 IsIgnore 行为不一致),导致未执行 fl_label_template_scope.sql 的库直接 500。
MCP 查库(测试库):
fl_label_template无AppliedPartnerType/AppliedRegionType列- 无
fl_label_template_partner/fl_label_template_region表 - 有
AppliedLocationType与fl_label_template_location
2.2 次因:列表 SQL 其它隐患(一并修复)
| # | 问题 | 后果 |
|---|---|---|
| 1 | 默认排序曾用 LastModificationTime ?? CreationTime |
SqlSugar 翻译失败 → 500 |
| 2 | Sorting 直接 OrderBy(input.Sorting) 拼 SQL |
非法字段 / 注入风险 |
| 3 | 空 templateIds 仍 Contains 查询 |
可能生成 IN () |
| 4 | 无效 partner/group 筛选未安全降级 | 未捕获异常 |
三、修复说明
1. 从 ORM 实体移除不存在列 + 强制列投影(核心)
| 改动 | 说明 |
|---|---|
FlLabelTemplateDbEntity |
删除 AppliedPartnerType / AppliedRegionType 属性 |
LabelTemplateQueryHelper.ProjectListColumns |
列表/详情/重复校验等只读查询 显式 Select 真实列,SQL 不再出现 scope 列 |
LabelTemplateScopeSchemaHelper |
探测列/表;已迁移时用 raw SQL 写入 scope 列 |
LabelTemplateAppService |
GetListAsync 在排序后调用 ProjectListColumns |
重要:若 Yi-SQL 日志仍出现
AppliedPartnerType,说明进程加载的是旧 DLL(FoodLabeling.Application未重新编译或未重启)。请先dotnet build通过后再完全停止并重启Yi.Abp.Web。
2. 安全排序
新增 ApplyLabelTemplateListSorting:
- 默认:
ORDER BY IFNULL(LastModificationTime, CreationTime) DESC, TemplateCode ASC Sorting白名单字段 + asc/desc
3. 列表查询健壮性
| 改动 | 说明 |
|---|---|
input ??= new()、pageSize 默认 10 |
入参/分页兜底 |
templateIds.Count > 0 再查 elements/items |
避免空 IN () |
ResolveFilteredLocationIdsForListAsync |
无效 partner/group 返回空列表 |
DeleteAsync |
仅当 scope 关联表存在时才删除 partner/region 行 |
4. 与 6-17 的关系
6-17 用 IsIgnore 试图跳过列映射,实测无效;6-18 改为实体不含列 + raw SQL 按需写入,与 fl_label.AppliedRegionType 处理方式一致。
四、接口说明
| 项目 | 内容 |
|---|---|
| 方法 | GET |
| 路径 | /api/app/label-template |
| 鉴权 | Bearer Token(Authorization: {data.token},data.token 已含 Bearer 前缀) |
入参(Query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
SkipCount |
int | 否 | 页码,从 1 起;第一页传 1 |
MaxResultCount |
int | 否 | 每页条数;<=0 时后端按 10 处理 |
Keyword |
string | 否 | 模板名称 / 编码模糊搜索 |
PartnerId |
string | 否 | 按 Company(fl_partner.Id)筛选 |
GroupId |
string | 否 | 按 Region(fl_group.Id)筛选 |
LocationId |
string | 否 | 按门店(location.Id)筛选;优先于 Partner/Region |
LabelType |
string | 否 | 如 PRICE / NUTRITION |
State |
bool | 否 | 启用状态 |
Sorting |
string | 否 | 白名单:TemplateName asc/desc、TemplateCode asc/desc、CreationTime asc/desc、LastModificationTime asc/desc;其它值忽略并走默认排序 |
筛选解析顺序:LocationId → GroupId → PartnerId;均未传则不按门店范围收窄(仍返回全部未删除模板,除非前端传了无效 Id 则返回空列表)。
出参(PagedResultWithPageDto<LabelTemplateGetListOutputDto>)
| 字段 | 说明 |
|---|---|
pageIndex |
当前页码 |
pageSize |
每页条数 |
totalCount |
总条数 |
totalPages |
总页数 |
items[] |
模板列表 |
items[] 主要字段
| 字段 | 说明 |
|---|---|
id / templateCode |
模板编码(前端主键) |
templateName |
模板名称 |
company / region / location |
适用范围展示;未迁移 scope 库时 Company/Region 为 All Companies / All Regions |
partnerIds / regionIds / locationIds |
对应 Id 数组 |
items / itemNames |
模板内控件名称 |
contentsCount |
控件数量 |
sizeText |
如 2x2inch |
lastEdited |
最近编辑时间(LastModificationTime ?? CreationTime) |
五、请求示例
登录获取 Token
curl -X POST "http://flus-test.3ffoodsafety.com/api/oauth/Login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "userName=admin&password=123456"
列表(第一页,默认排序)
curl -G "http://flus-test.3ffoodsafety.com/api/app/label-template" \
-H "Authorization: Bearer {token}" \
--data-urlencode "SkipCount=1" \
--data-urlencode "MaxResultCount=10"
带 Company 筛选
curl -G "http://flus-test.3ffoodsafety.com/api/app/label-template" \
-H "Authorization: Bearer {token}" \
--data-urlencode "SkipCount=1" \
--data-urlencode "MaxResultCount=10" \
--data-urlencode "PartnerId={fl_partner.Id}"
指定排序
curl -G "http://flus-test.3ffoodsafety.com/api/app/label-template" \
-H "Authorization: Bearer {token}" \
--data-urlencode "SkipCount=1" \
--data-urlencode "MaxResultCount=10" \
--data-urlencode "Sorting=TemplateName asc"
响应片段(示例)
{
"pageIndex": 1,
"pageSize": 10,
"totalCount": 3,
"totalPages": 1,
"items": [
{
"id": "tpl_n0b5h9_mpyssitm",
"templateCode": "tpl_n0b5h9_mpyssitm",
"templateName": "Retail Label w/Price Copy",
"company": "All Companies",
"region": "All Regions",
"location": "Ordos Airport, Store B",
"items": "Label Name, Price, Barcode",
"contentsCount": 5,
"sizeText": "2x2inch",
"lastEdited": "2026-06-04T08:30:00"
}
]
}
六、验证步骤
重新编译并重启(必做):
cd "美国版/Food Labeling Management Code/Yi.Abp.Net8/src/Yi.Abp.Web" dotnet build停止正在运行的 Web 进程后重新启动;确认 Yi-SQL 中 不再出现
AppliedPartnerType。部署包含 6-17 + 6-18 的后端并重启。
调用
GET /api/app/label-template?SkipCount=1&MaxResultCount=10→ 应 200,totalCount >= 0。Web Label Templates 列表可加载,不再出现红色 Failed to load label templates。
传无效
PartnerId/GroupId→ 200 且items=[](非 500)。不传
Sorting→ 按最近编辑时间降序;传Sorting=TemplateName asc→ 按名称升序。分页:第 2 页
SkipCount=2,totalCount与 UI 一致。
SQL 抽查(修复后 ORM 应生成的列)
SELECT Id, TemplateCode, TemplateName, AppliedLocationType,
IFNULL(LastModificationTime, CreationTime) AS LastEdited
FROM fl_label_template
WHERE IsDeleted = 0
ORDER BY IFNULL(LastModificationTime, CreationTime) DESC, TemplateCode ASC
LIMIT 10;
不应再出现 AppliedPartnerType / AppliedRegionType。
检查 scope 是否已迁移
SELECT COUNT(*) AS scope_table_cnt
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME IN ('fl_label_template_partner', 'fl_label_template_region');
scope_table_cnt = 0 时行为与 6-17 一致:仅 Location 维度落库与展示。
七、涉及代码
| 文件 | 说明 |
|---|---|
Helpers/LabelTemplateQueryHelper.cs |
新增 ProjectListColumns 强制 SQL 列白名单 |
Services/DbModels/FlLabelTemplateDbEntity.cs |
移除 Partner/Region 列属性 |
Helpers/LabelTemplateScopeSchemaHelper.cs |
列/表探测 + SetAppliedScopeTypesAsync raw SQL |
Services/LabelTemplateAppService.cs |
安全排序、Create/Update 写 scope 列、列表健壮性 |
Helpers/LabelTemplateScopeHelper.cs |
未迁移库 scope 过滤/展示(6-17) |
Helpers/LocationScopeBindingHelper.cs |
ResolveFilteredLocationIdsForListAsync |
Helpers/LabelTemplateListItemsHelper.cs |
Items 列 |
Dtos/LabelTemplate/LabelTemplateGetListInputVo.cs |
PartnerId 筛选 |
八、数据库迁移(可选)
需完整 Company / Region 三维 scope 时,执行:
美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_template_scope.sql
未执行前:列表可正常返回(6-17 + 6-18),但无法持久化 Company/Region 多选明细。
九、App 标签预览 POST /api/app/us-app-labeling/preview
现象
App 标签预览页调用 preview 失败(500 或 400),常与 label-template 列表 同源:加载模板头时 ORM 仍 SELECT 不存在的 AppliedPartnerType / AppliedRegionType。
另有两项逻辑缺陷(与 6-11 文档不一致):
| # | 问题 | 后果 |
|---|---|---|
| 1 | LabelAppService.PreviewAsync 全表查询 FlLabelTemplateDbEntity |
未迁移 scope 列时 Unknown column → 500 |
| 2 | UsAppLabelingAppService.PreviewAsync 未向 _labelAppService.PreviewAsync 传 locationId |
模板含 Company 自动生成元素时报 「预览/打印需要 locationId 以填充 Company 信息」 |
| 3 | 出参 labelId 仍返回 fl_label.Id(GUID) |
与 Print Log 当日序号 yyyyMMdd-n 不一致 |
修复说明
| 改动 | 说明 |
|---|---|
LabelAppService.PreviewAsync |
模板头查询改用 LabelTemplateQueryHelper.ProjectListColumns |
UsAppLabelingAppService.PreviewAsync |
传入 LocationId;labelId 改用 ReportsPrintLogDailyLabelIdHelper.ResolveNextDailyLabelIdAsync |
UsAppLabelingAppService.PrintAsync |
解析模板时同步传入 LocationId(打印与预览 Company 填充一致) |
DashboardAppService |
模板统计 Count 同样走 ProjectListColumns |
接口说明
| 项目 | 内容 |
|---|---|
| 方法 | POST |
| 路径 | /api/app/us-app-labeling/preview |
| 鉴权 | App Bearer Token |
入参(Body:UsAppLabelPreviewInputVo)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
locationId |
string | 是 | 当前门店 Id(location.Id) |
labelCode |
string | 是 | 标签编码(fl_label.LabelCode) |
productId |
string | 否 | 预览产品 Id;不传则取标签绑定第一个产品 |
baseTime |
DateTime | 否 | 日期/时间控件基准;也用于计算当日 labelId 序号;未传为服务器当前时间 |
printInputJson |
object | 否 | PRINT_INPUT 元素用户输入 |
出参(UsAppLabelPreviewDto)
| 字段 | 说明 |
|---|---|
labelId |
门店当日下一个打印序号 yyyyMMdd-n(预览不落库) |
locationId / labelCode |
回传入参 |
template |
已解析 AUTO_DB / PRINT_INPUT 的模板结构(含 Company 自动填充) |
labelLastEdited |
标签最近编辑时间 |
| 其它 | typeName、productName、templateProductDefaultValues 等 |
labelId 规则
与 6-11、get-print-log-list 一致:{baseTime 日期 yyyyMMdd}-{当日已有打印任务数 + 1}。
Company 自动元素
模板含 Company 自动生成控件时,后端根据 locationId 查 fl_partner 填充 config.text(详见 6-11 第二节)。
请求示例
curl -X POST "http://flus-test.3ffoodsafety.com/api/app/us-app-labeling/preview" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"locationId": "550e8400-e29b-41d4-a716-446655440000",
"labelCode": "LBL0001",
"productId": "PROD001",
"baseTime": "2026-06-18T09:00:00"
}'
响应片段(示例)
{
"labelId": "20260618-3",
"locationId": "550e8400-e29b-41d4-a716-446655440000",
"labelCode": "LBL0001",
"labelLastEdited": "2026-06-01T08:30:09",
"template": {
"id": "tpl_xxx",
"width": 2,
"height": 2,
"unit": "inch",
"elements": []
}
}
验证步骤
- 停服 →
dotnet build→ 重启(同 label-template 一节)。 - App 进入标签预览页,Network 中 preview 应 200。
- 响应
labelId为yyyyMMdd-n,非 GUID。 - 模板含 Company 元素时,
template.elements中对应config.text为门店所属公司名。 - 当日已有 N 条打印任务时,preview 返回
-{N+1}。
涉及代码(preview)
| 文件 | 说明 |
|---|---|
Services/UsAppLabelingAppService.cs |
PreviewAsync / PrintAsync 传 LocationId、当日 labelId |
Services/LabelAppService.cs |
PreviewAsync 模板头 ProjectListColumns |
Helpers/LabelTemplateQueryHelper.cs |
列投影 |
Helpers/ReportsPrintLogDailyLabelIdHelper.cs |
ResolveNextDailyLabelIdAsync |
Helpers/PartnerCompanyDisplayHelper.cs |
Company 自动填充 |
十、App 打印日志 POST /api/app/us-app-labeling/get-print-log-list
现象
Postman 传 printDate: "2026-06-16" 返回 totalCount: 0,误以为接口异常。
根因(查库核对)
门店 3a218397-8dda-a378-e024-ef89bcef8d24 在测试库中:
自然日(DATE(IFNULL(PrintedAt, CreationTime))) |
记录数 |
|---|---|
2026-06-01 |
7 |
2026-05-31 |
3 |
2026-06-16 |
0 |
接口按入参日期筛选时,该日无数据则返回空列表,行为正确。 文档/示例误用 2026-06-16,应改用 2026-06-01 验证。
另:PrintedAt 入库多为 null,筛选实际走 CreationTime。
本轮修复
| 改动 | 说明 |
|---|---|
| 日期条件 | 改用 MySQL DATE(IFNULL(t.PrintedAt, t.CreationTime)) = 'yyyy-MM-dd',避免 DateTime 区间比较时区偏差 |
printDateDay |
新增字符串入参(yyyy-MM-dd),优先于 printDate,避免 JSON 仅日期 UTC 歧义 |
| 未传日期 | printDate 与 printDateDay 均未传时不按日过滤(返回该门店全部打印记录,兼容 App 未传参) |
labelId |
列表出参改为当日序号 yyyyMMdd-n;labelEntityId 为 fl_label.Id |
请求示例(有数据的日期)
curl -X POST "http://192.168.1.4:19001/api/app/us-app-labeling/get-print-log-list" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"locationId": "3a218397-8dda-a378-e024-ef89bcef8d24",
"skipCount": 1,
"maxResultCount": 20,
"printDateDay": "2026-06-01"
}'
或使用 "printDate": "2026-06-01"(等价)。
权限说明
非 admin / 非 Partner 角色时,仅返回 CreatedBy = 当前用户 的记录;若 Token 用户不是打印人,即使日期正确也会空列表。
部署
修改在 FoodLabeling.Application,需 停服 → dotnet build → 重启 后 Postman 才生效。
涉及代码
| 文件 | 说明 |
|---|---|
Helpers/ReportsPrintLogDailyLabelIdHelper.cs |
ResolvePrintLogFilterCalendarDay、ApplyPrintTaskCalendarDayFilter |
Dtos/UsAppLabeling/PrintLogGetListInputVo.cs |
PrintDateDay |
Services/UsAppLabelingAppService.cs |
GetPrintLogListAsync |
关联文档
- App Preview labelId / Company:
项目相关文档/6-11代码优化.md - 第一轮 scope 兼容:
项目相关文档/6-17代码优化.md(第一节、第二节 get-print-log-list) - 三维 scope 业务规则:
项目相关文档/6-4代码优化.md - 分页约定:
Helpers/PagedQueryConvention.cs