property.list.query 销售非负责人规则改为 1:1 复刻老代码 saleFlag 分支(id IN 自建∪代管物业,互斥 return);新增销售视角种子 ak,dev 实测 total=14 与老语义 SQL 逐 id 一致

This commit is contained in:
xiaoqu
2026-09-03 14:00:13 +08:00
parent fd7ec29374
commit ddd4b3b4ed
5 changed files with 84 additions and 30 deletions
+3 -1
View File
@@ -61,7 +61,8 @@ xiaoqu-agent-service/
|---|---|---|
| `isAgentAuthority(authorityId)` | `AuthorityServiceImpl.checkAuthorityIsagent` | 权限组 isagent=1 → 代理商视角 |
| `visibleAgentIds(user)` | `getByIsAgent` 主重载 | isagent!=1 返回 null(不过滤);否则 自己 + 直属下级(supervisor_id 一层)+ m2 warehouse_agent_auth 代管 manager_agent_id |
| `isSalesNonLeader(user)` | saleFlag 逻辑(:382-427) | Redis db5 `Dictionary` hash 的 authDeptId 命中 ",deptId," 且 authDeptPostId 不含 "postId,";字典缺失按不限制处理并告警 |
| `isSalesNonLeader(user)` | saleFlag 判定(:703-710) | Redis db5 `Dictionary` hash 的 authDeptId 命中 ",deptId," 且 authDeptPostId 不含 "postId,";字典缺失按不限制处理并告警 |
| `applyPropertyRowFilter` 销售分支 | saleFlag 分支(:716-735,互斥 return) | **1:1 复刻(2026-09-03 决策)**:id IN (create_id=自己 ∪ [代管授权非空时] agent_id IN (下级+代管移除自己) 的物业),空集合则 id IN (0);命中后不再叠加 agent_id/代管物业 OR 结构 |
| `isPropertyUser(user)` | enterprise_type=2 规则(§3.3 通用规则,保留备用) | 物业用户;**property.list.query 不使用本规则**(物业用户不查此列表,与老 /property/listByPage 一致,2026-09-03 决策) |
`agent.list.query` 的行权限照搬老系统 `AgentController.listByPage`:isagent=1 → `supervisor_id = 用户agentId`(只查直属下级);isagent=0 → 不过滤;入参传 `id` → 精确查询(可查到自身,不加 supervisor 限制)。
@@ -105,6 +106,7 @@ nohup java -jar target/xiaoqu-agent-service.jar > target/app.log 2>&1 &
| 总部视角(isagent=0) | 46 代理商管理端 | `xqak_1d21057ad83203dbeb767eb5069701046b27debdc08a733c` |
| 代理商视角(isagent=1,agent_id=1,26 个直属下级) | 1000 斤斤计较 | `xqak_7df02ccee2133dc2cf951db12e3958557bdf2d30a91e7f08` |
| 无任何 scope(验证拒绝) | 1000 斤斤计较 | `xqak_5bdc0eac8c1cab535f22dc3e9757f72b3127de43a14997a7` |
| 销售非负责人(dept 4/post 103,代管 agent 19) | 1727 北京销售1 | `xqak_cb006faf86c0db6f2f47b3eb8872f8b5d495b0c05a6fb52c` |
## curl 验证(Streamable HTTP 握手)
+9 -2
View File
@@ -6,6 +6,8 @@
-- HQ user_id=46 xqak_1d21057ad83203dbeb767eb5069701046b27debdc08a733c
-- AGENT user_id=1000 xqak_7df02ccee2133dc2cf951db12e3958557bdf2d30a91e7f08
-- NOSCOPE user_id=1000 xqak_5bdc0eac8c1cab535f22dc3e9757f72b3127de43a14997a7 (未授权任何 scope,验证拒绝)
-- SALES user_id=1727 xqak_cb006faf86c0db6f2f47b3eb8872f8b5d495b0c05a6fb52c (北京销售1,dept_id=4 命中 authDeptId,
-- post_id=103 不在 authDeptPostId → 销售非负责人;warehouse_agent_auth 代管 agent 19)
-- 依赖:tool 表已由应用启动时 ToolSyncService 同步(sys.ping/agent.count/agent.list.query)
-- =============================================================
@@ -26,6 +28,10 @@ INSERT INTO agent_api_key (user_id, key_prefix, key_hash, memo, status, create_i
SELECT 1000, 'xqak_5bdc', '85733c4eaab02e956bc555e0f25e5043629f9b8f8ad9ca39046735f5e817ca9c', 'dev 测试:无任何 scope 授权', 1, 0
WHERE NOT EXISTS (SELECT 1 FROM agent_api_key WHERE key_hash = '85733c4eaab02e956bc555e0f25e5043629f9b8f8ad9ca39046735f5e817ca9c');
INSERT INTO agent_api_key (user_id, key_prefix, key_hash, memo, status, create_id)
SELECT 1727, 'xqak_cb00', '9fd25466ad4913b0c48e60d853b1bb27ba75bec426a117978e8c95687917bada', 'dev 测试:销售非负责人(北京销售1, dept 4/post 103, 代管 agent 19)', 1, 0
WHERE NOT EXISTS (SELECT 1 FROM agent_api_key WHERE key_hash = '9fd25466ad4913b0c48e60d853b1bb27ba75bec426a117978e8c95687917bada');
-- 作用域:运营
INSERT INTO agent_scope (name, code, memo, status, create_id)
SELECT '运营', 'operation', 'dev 种子:运营作用域', 1, 0
@@ -37,11 +43,12 @@ SELECT s.id, t.id, 0 FROM agent_scope s JOIN tool t
WHERE s.code = 'operation' AND t.name IN ('sys.ping', 'agent.count', 'agent.list.query', 'property.list.query')
AND NOT EXISTS (SELECT 1 FROM scope_tool st WHERE st.scope_id = s.id AND st.tool_id = t.id);
-- token-作用域:HQ 与 AGENT 两把 token 授权"运营";NOSCOPE 不授权
-- token-作用域:HQ / AGENT / SALES 三把 token 授权"运营";NOSCOPE 不授权
INSERT INTO token_scope (token_id, scope_id, create_id)
SELECT k.id, s.id, 0 FROM agent_api_key k JOIN agent_scope s
WHERE k.key_hash IN ('9068d5a6406afdb853cdc9f5e45e16a5191255e2feb1aba171cd66ea13d5711c',
'00ab77ba18c11c8870500e1c09a9e25747e4cf0d8f1c88ae255a2beca6eef007')
'00ab77ba18c11c8870500e1c09a9e25747e4cf0d8f1c88ae255a2beca6eef007',
'9fd25466ad4913b0c48e60d853b1bb27ba75bec426a117978e8c95687917bada')
AND s.code = 'operation'
AND NOT EXISTS (SELECT 1 FROM token_scope ts WHERE ts.token_id = k.id AND ts.scope_id = s.id);
@@ -51,7 +51,7 @@ public class PropertyTools implements ToolObject {
@Tool(name = "property.list.query", description = "查询物业(客户)列表(分页),数据来源 m0 库 properyt 表。"
+ "行级权限:代理商身份的用户(权限组 isagent=1)只能看到 自己+直属下级代理+被授权代管代理 名下的物业,"
+ "以及被授权代管的物业(agent_manage_district_auth);总部身份(isagent=0)可看全部;"
+ "销售部门非负责人只能看自己创建的物业。"
+ "销售部门非负责人只能看到 自己创建的物业 及 被授权代管代理名下的物业(与其余规则互斥)。"
+ "分页:page 从 1 开始,rows 默认 20、上限 100,超出自动截断为 100 并在返回的 notice 字段提示。"
+ "返回 total(符合条件的总数)、records(本页记录)。")
public Map<String, Object> propertyListQuery(
@@ -10,6 +10,7 @@ import me.iiv.agent.entity.m2.WarehouseAgentAuth;
import me.iiv.agent.mapper.m0.AgentManageDistrictAuthMapper;
import me.iiv.agent.mapper.m0.AgentMapper;
import me.iiv.agent.mapper.m0.AuthorityMapper;
import me.iiv.agent.mapper.m0.ProperytMapper;
import me.iiv.agent.mapper.m2.WarehouseAgentAuthMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,16 +42,18 @@ public class PermissionService {
final AgentMapper agentMapper;
final WarehouseAgentAuthMapper warehouseAgentAuthMapper;
final AgentManageDistrictAuthMapper agentManageDistrictAuthMapper;
final ProperytMapper properytMapper;
final StringRedisTemplate redisTemplate;
public PermissionService(AuthorityMapper authorityMapper, AgentMapper agentMapper,
WarehouseAgentAuthMapper warehouseAgentAuthMapper,
AgentManageDistrictAuthMapper agentManageDistrictAuthMapper,
StringRedisTemplate redisTemplate) {
ProperytMapper properytMapper, StringRedisTemplate redisTemplate) {
this.authorityMapper = authorityMapper;
this.agentMapper = agentMapper;
this.warehouseAgentAuthMapper = warehouseAgentAuthMapper;
this.agentManageDistrictAuthMapper = agentManageDistrictAuthMapper;
this.properytMapper = properytMapper;
this.redisTemplate = redisTemplate;
}
@@ -73,6 +76,15 @@ public class PermissionService {
return null;
}
Set<Long> ids = new LinkedHashSet<>();
ids.addAll(subordinateAndSelfIds(user));
ids.addAll(managedAuths(user.getId()).stream()
.map(WarehouseAgentAuth::getManagerAgentId).collect(Collectors.toList()));
return new ArrayList<>(ids);
}
/** 直属下级代理 id + 自己 agentId(老 :682-695,supervisor_id=自己,只取一层) */
private List<Long> subordinateAndSelfIds(User user) {
List<Long> ids = new ArrayList<>();
if (user.getAgentId() != null) {
List<Agent> subordinates = agentMapper.selectList(new LambdaQueryWrapper<Agent>()
.eq(Agent::getSoftDelete, 0)
@@ -80,15 +92,18 @@ public class PermissionService {
ids.addAll(subordinates.stream().map(Agent::getId).collect(Collectors.toList()));
ids.add(user.getAgentId());
}
List<WarehouseAgentAuth> auths = warehouseAgentAuthMapper.selectList(new LambdaQueryWrapper<WarehouseAgentAuth>()
return ids;
}
/** m2 warehouse_agent_auth 代管授权(user_id, soft_delete=0),老 :696-702 */
private List<WarehouseAgentAuth> managedAuths(Long userId) {
return warehouseAgentAuthMapper.selectList(new LambdaQueryWrapper<WarehouseAgentAuth>()
.eq(WarehouseAgentAuth::getSoftDelete, 0)
.eq(WarehouseAgentAuth::getUserId, user.getId()));
ids.addAll(auths.stream().map(WarehouseAgentAuth::getManagerAgentId).collect(Collectors.toList()));
return new ArrayList<>(ids);
.eq(WarehouseAgentAuth::getUserId, userId));
}
/**
* 销售部门非负责人判定(老系统 getByIsAgent 内 saleFlag 逻辑):
* 销售部门非负责人判定(老系统 getByIsAgent 内 saleFlag 逻辑,:703-710):
* Redis db5 Dictionary hash 的 authDeptId 命中 ",deptId," 且 authDeptPostId 不命中 "postId,"。
* 字典缺失时按"不限制"处理并告警(与老系统 NPE 直接报错的行为不同,属于有意收敛)。
*/
@@ -133,31 +148,61 @@ public class PermissionService {
}
/**
* 物业(properyt 表)行级过滤组合,语义对应老系统 PropertyController.listByPage 使用的
* getByIsAgent(agentFlag, agentId, wrapper, user, propertyFlag=true, className="id", redisTemplate):
* 物业(properyt 表)行级过滤组合,1:1 复刻老系统 PropertyController.listByPage 使用的
* getByIsAgent(agentFlag, agentId, wrapper, user, propertyFlag=true, className="id", redisTemplate)
* (WrapperUtils.java:679-765):
* <ol>
* <li>isagent=1:agent_id IN (自己+下级+warehouse_agent_auth 代管),
* 若存在代管物业则 OR id IN (agent_manage_district_auth 代管物业 id 集合);</li>
* <li>isagent!=1(总部):不加 agent/id 过滤;</li>
* <li>销售部门非负责人:再 AND create_id = 自己
* (注:老代码 saleFlag 分支是 id IN (自建 ∪ 代管 agent 的物业),
* 此处按设计文档 §3.3"只看 create_id=自己"收窄)。</li>
* <li>isagent!=1(总部):不加任何过滤(老 :680 if(agentFlag) 外层);</li>
* <li>销售非负责人(saleFlag=true,:716-735,互斥 return,不进第 3 条):
* id IN (create_id=自己 的物业 ∪ [代管授权非空时] agent_id IN (下级+代管,移除自己) 的物业),
* 集合为空时 id IN (0);</li>
* <li>其余 isagent=1:agent_id IN (自己+下级+代管),
* 若存在 agent_manage_district_auth 代管物业则 AND (agent_id IN ids OR id IN 代管物业ids),
* 否则 agent_id IN ids(:747-760 else 分支,className="id")。</li>
* </ol>
* 不含 enterprise_type=2(物业用户)限制:物业用户不查此列表,与老接口语义一致
* (2026-09-03 用户决策;§3.3 通用规则保留于 {@link #isPropertyUser})。
* 不含 enterprise_type=2(物业用户)限制:物业用户不查此列表,与老接口语义一致(2026-09-03 用户决策)。
* 销售非负责人规则 2026-09-03 用户决策:1:1 复刻老代码(不再按 §3.3 收窄为 AND create_id=自己)。
*/
public void applyPropertyRowFilter(LambdaQueryWrapper<Properyt> wrapper, User user) {
List<Long> agentIds = visibleAgentIds(user);
if (agentIds != null) {
List<Long> propertyIds = managedPropertyIds(agentIds);
if (!propertyIds.isEmpty()) {
wrapper.and(w -> w.in(Properyt::getAgentId, agentIds).or().in(Properyt::getId, propertyIds));
} else {
wrapper.in(Properyt::getAgentId, agentIds);
}
if (!isAgentAuthority(user.getAuthorityId())) {
return;
}
// ids 组装:下级 + 自己 + 代管(老 :682-702)
List<Long> ids = new ArrayList<>(subordinateAndSelfIds(user));
List<WarehouseAgentAuth> agentlist = managedAuths(user.getId());
ids.addAll(agentlist.stream().map(WarehouseAgentAuth::getManagerAgentId).collect(Collectors.toList()));
// 老 :716-735 saleFlag 分支(互斥,直接 return)
if (isSalesNonLeader(user)) {
wrapper.eq(Properyt::getCreateId, user.getId());
List<Long> propertyIds = properytMapper.selectList(new LambdaQueryWrapper<Properyt>()
.eq(Properyt::getSoftDelete, 0)
.eq(Properyt::getCreateId, user.getId())
.select(Properyt::getId))
.stream().map(Properyt::getId).collect(Collectors.toList());
if (!agentlist.isEmpty()) {
ids.remove(user.getAgentId());
if (!ids.isEmpty()) {
propertyIds.addAll(properytMapper.selectList(new LambdaQueryWrapper<Properyt>()
.eq(Properyt::getSoftDelete, 0)
.in(Properyt::getAgentId, ids)
.select(Properyt::getId))
.stream().map(Properyt::getId).collect(Collectors.toList()));
}
}
if (propertyIds.isEmpty()) {
wrapper.in(Properyt::getId, 0);
} else {
wrapper.in(Properyt::getId, propertyIds);
}
return;
}
// 老 :747-760 else 分支(className="id")
List<Long> managedPropertyIds = managedPropertyIds(ids);
if (!managedPropertyIds.isEmpty()) {
wrapper.and(w -> w.in(Properyt::getAgentId, ids).or().in(Properyt::getId, managedPropertyIds));
} else {
wrapper.in(Properyt::getAgentId, ids);
}
}
}
@@ -210,7 +210,7 @@ xiaoqu-agent-service/
**`property.list.query`**(分类 customer,读)
- 语义照搬:完整版 `getByIsAgent`(下级代理 + warehouse_agent_auth 代管 + agent_manage_district_auth 代管物业 + 销售非负责人限 create_id)。**不含** enterprise_type=2(物业用户限自己物业)限制——物业用户不查此列表,与老接口 `/property/listByPage`(PropertyController.java:95)语义一致(2026-09-03 决策;§3.3 通用规则表保留该条,仅此处不适用)。
- 语义照搬:完整版 `getByIsAgent`(下级代理 + warehouse_agent_auth 代管 + agent_manage_district_auth 代管物业 + 销售非负责人规则)。销售非负责人规则 2026-09-03 决策:**1:1 复刻老代码** saleFlag 分支(WrapperUtils.java:716-735)——可见范围 = 自己创建的物业(create_id=自己)∪ 代管授权非空时 agent_id IN (下级+代管,移除自己) 的物业,即 `id IN (...)` 结构,且与其余规则互斥(saleFlag 命中即 return);不再按 §3.3 表格收窄为 AND create_id=自己。**不含** enterprise_type=2(物业用户限自己物业)限制——物业用户不查此列表,与老接口 `/property/listByPage`(PropertyController.java:95)语义一致(2026-09-03 决策;§3.3 通用规则表保留该条,仅此处不适用)。
- 入参:`name`(模糊)、`agentId`、`serialnumber`、`province`、`city`、`contact`、`phone`、`page`、`rows`。
- 出参:id、name、serialnumber、agentName、createName(销售)、province/city/countyseat、address、contact、phone、createTime。
- 已裁剪:开票信息、银行账户。