property.list.query 销售非负责人规则改为 1:1 复刻老代码 saleFlag 分支(id IN 自建∪代管物业,互斥 return);新增销售视角种子 ak,dev 实测 total=14 与老语义 SQL 逐 id 一致
This commit is contained in:
@@ -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 握手)
|
||||
|
||||
|
||||
@@ -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。
|
||||
- 已裁剪:开票信息、银行账户。
|
||||
|
||||
Reference in New Issue
Block a user