| | |
| | | import com.ruoyi.common.security.annotation.Logical; |
| | | import com.ruoyi.common.security.annotation.RequiresPermissions; |
| | | import com.ruoyi.other.api.domain.TGroupCompany; |
| | | import com.ruoyi.other.service.CompanyAccountAccessService; |
| | | import com.ruoyi.other.service.TGroupCompanyService; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.util.StringUtils; |
| | |
| | | |
| | | @Resource |
| | | private TGroupCompanyService groupCompanyService; |
| | | @Resource |
| | | private CompanyAccountAccessService companyAccountAccessService; |
| | | |
| | | /** |
| | | * 分页查询公司 |
| | | * <p>公司账号(roleType=5)只返回本公司数据,非公司账号保持原有查询范围</p> |
| | | */ |
| | | @RequiresPermissions(value = {"/companyManagement/list", "/fleet/list", "/vinManagement/list", "/chargingCard/list"}, logical = Logical.OR) |
| | | @GetMapping("/page") |
| | |
| | | @RequestParam(value = "contactPhone", required = false) String contactPhone, |
| | | @RequestParam(value = "status", required = false) Integer status) { |
| | | PageInfo<TGroupCompany> page = new PageInfo<>(current, size); |
| | | // 公司账号(roleType=5)只返回本公司数据;平台/合作商等账号保持原查询范围 |
| | | Integer currentCompanyId = companyAccountAccessService.getCurrentCompanyId(); |
| | | groupCompanyService.lambdaQuery() |
| | | .eq(currentCompanyId != null, TGroupCompany::getId, currentCompanyId) |
| | | .eq(groupId != null, TGroupCompany::getGroupId, groupId) |
| | | .like(StringUtils.hasText(name), TGroupCompany::getName, name) |
| | | .like(StringUtils.hasText(contactName), TGroupCompany::getContactName, contactName) |
| | |
| | | if (old == null) { |
| | | return AjaxResult.error("公司不存在"); |
| | | } |
| | | // 编辑时账号只读(前端禁用),此处兼容外部调用方传入账号的场景:唯一性校验(排除自身) |
| | | // 编辑支持修改登录账号:公司表唯一性校验(排除自身) |
| | | if (StringUtils.hasText(company.getAccount())) { |
| | | if (accountExists(company.getAccount(), company.getId())) { |
| | | return AjaxResult.error("登录账号不能重复"); |
| | |
| | | } |
| | | String account = StringUtils.hasText(company.getAccount()) ? company.getAccount() : old.getAccount(); |
| | | if (StringUtils.hasText(account)) { |
| | | syncCompanyUserOnEdit(company, account); |
| | | String error = syncCompanyUserOnEdit(company, old.getAccount(), account); |
| | | if (error != null) { |
| | | return AjaxResult.error(error); |
| | | } |
| | | } |
| | | updateById(company); |
| | | return AjaxResult.success(); |
| | | } |
| | | |
| | | /** |
| | | * 编辑公司时同步登录账号:存在则仅更新账号资料(不修改密码),不存在则补建 |
| | | * 编辑公司时同步登录账号:按旧账号定位用户后更新为新账号(支持改账号); |
| | | * 密码有值时重置(留空不修改);用户不存在则按新账号补建 |
| | | * |
| | | * @return 冲突等业务错误信息,null 表示成功 |
| | | */ |
| | | private void syncCompanyUserOnEdit(TGroupCompany company, String account) { |
| | | R<SysUser> userR = sysUserClient.queryUserByUserName(account); |
| | | private String syncCompanyUserOnEdit(TGroupCompany company, String oldAccount, String newAccount) { |
| | | // 账号变更时校验新账号未被其他用户占用(updateSysUser 无唯一性校验,需在此前置拦截) |
| | | if (StringUtils.hasText(oldAccount) && !newAccount.equals(oldAccount)) { |
| | | R<SysUser> occupantR = sysUserClient.queryUserByUserName(newAccount); |
| | | SysUser occupant = occupantR == null ? null : occupantR.getData(); |
| | | if (occupant != null) { |
| | | R<SysUser> ownerR = sysUserClient.queryUserByUserName(oldAccount); |
| | | SysUser owner = ownerR == null ? null : ownerR.getData(); |
| | | if (owner == null || !occupant.getUserId().equals(owner.getUserId())) { |
| | | return "登录账号不能重复"; |
| | | } |
| | | } |
| | | } |
| | | // 按旧账号定位既有用户(账号未变时与新账号一致),避免改名后误走补建导致双账号 |
| | | R<SysUser> userR = sysUserClient.queryUserByUserName(StringUtils.hasText(oldAccount) ? oldAccount : newAccount); |
| | | SysUser user = userR == null ? null : userR.getData(); |
| | | if (user == null) { |
| | | if (!StringUtils.hasText(company.getPassword())) { |
| | |
| | | } |
| | | Long roleId = resolveCompanyRoleId(); |
| | | SysUser newUser = new SysUser(); |
| | | newUser.setUserName(account); |
| | | newUser.setUserName(newAccount); |
| | | newUser.setNickName(company.getName()); |
| | | newUser.setPhonenumber(company.getContactPhone()); |
| | | newUser.setPassword(company.getPassword()); |
| | |
| | | newUser.setRoleType(COMPANY_ROLE_TYPE); |
| | | newUser.setObjectId(company.getId()); |
| | | newUser.setPassWordUpdate(new Date()); |
| | | R<Long> r = sysUserClient.addSysUser(newUser); |
| | | if (r == null || 200 != r.getCode()) { |
| | | throw new RuntimeException(r == null ? "创建登录账号失败" : r.getMsg()); |
| | | R<Long> addR = sysUserClient.addSysUser(newUser); |
| | | if (addR == null || 200 != addR.getCode()) { |
| | | throw new RuntimeException(addR == null ? "创建登录账号失败" : addR.getMsg()); |
| | | } |
| | | return; |
| | | return null; |
| | | } |
| | | // 远程查询返回的密码字段可能是哈希或脱敏值,显式清空避免再次写回 |
| | | user.setPassword(null); |
| | | user.setPassWordUpdate(null); |
| | | user.setUserName(account); |
| | | user.setUserName(newAccount); |
| | | user.setNickName(company.getName()); |
| | | user.setPhonenumber(company.getContactPhone()); |
| | | R<Boolean> r = sysUserClient.updateSysUser(user); |
| | | if (r == null || 200 != r.getCode()) { |
| | | throw new RuntimeException(r == null ? "更新登录账号失败" : r.getMsg()); |
| | | R<Boolean> updateR = sysUserClient.updateSysUser(user); |
| | | if (updateR == null || 200 != updateR.getCode()) { |
| | | throw new RuntimeException(updateR == null ? "更新登录账号失败" : updateR.getMsg()); |
| | | } |
| | | // 密码有值时重置(前端已 HMAC-MD5,由 system 侧 BCrypt 落库,与新增链路一致) |
| | | if (StringUtils.hasText(company.getPassword())) { |
| | | SysUser pwdUser = new SysUser(); |
| | | pwdUser.setUserId(user.getUserId()); |
| | | pwdUser.setPassword(company.getPassword()); |
| | | R pwdR = sysUserClient.resetPassword(pwdUser); |
| | | if (pwdR == null || 200 != pwdR.getCode()) { |
| | | throw new RuntimeException(pwdR == null ? "重置登录密码失败" : pwdR.getMsg()); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| New file |
| | |
| | | package com.ruoyi.other.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.toolkit.support.SFunction; |
| | | import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; |
| | | import com.ruoyi.common.core.web.domain.AjaxResult; |
| | | import com.ruoyi.common.core.web.page.PageInfo; |
| | | import com.ruoyi.other.api.domain.TGroupCompany; |
| | | import com.ruoyi.other.service.CompanyAccountAccessService; |
| | | import com.ruoyi.other.service.TGroupCompanyService; |
| | | import org.junit.jupiter.api.BeforeEach; |
| | | import org.junit.jupiter.api.DisplayName; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.ArgumentMatchers.anyBoolean; |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.never; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | /** |
| | | * 公司列表查询接口数据范围测试:公司账号(roleType=5)只查本公司数据。 |
| | | */ |
| | | class TGroupCompanyControllerTest { |
| | | |
| | | private TGroupCompanyController controller; |
| | | |
| | | private CompanyAccountAccessService companyAccountAccessService; |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | private final LambdaQueryChainWrapper<TGroupCompany> chain = |
| | | (LambdaQueryChainWrapper<TGroupCompany>) mock(LambdaQueryChainWrapper.class); |
| | | |
| | | @BeforeEach |
| | | void setUp() { |
| | | TGroupCompanyService groupCompanyService = mock(TGroupCompanyService.class); |
| | | when(groupCompanyService.lambdaQuery()).thenReturn(chain); |
| | | when(chain.eq(anyBoolean(), any(), any())).thenReturn(chain); |
| | | when(chain.like(anyBoolean(), any(), any())).thenReturn(chain); |
| | | when(chain.orderByDesc(any(SFunction.class))).thenReturn(chain); |
| | | companyAccountAccessService = mock(CompanyAccountAccessService.class); |
| | | controller = new TGroupCompanyController(); |
| | | ReflectionTestUtils.setField(controller, "groupCompanyService", groupCompanyService); |
| | | ReflectionTestUtils.setField(controller, "companyAccountAccessService", companyAccountAccessService); |
| | | } |
| | | |
| | | @Test |
| | | @DisplayName("公司账号查询公司列表时强制追加本公司ID条件") |
| | | void companyAccountQueriesOwnCompanyOnly() { |
| | | when(companyAccountAccessService.getCurrentCompanyId()).thenReturn(9); |
| | | |
| | | AjaxResult<PageInfo<TGroupCompany>> result = controller.page(1, 10, null, null, null, null, null); |
| | | |
| | | assertNotNull(result); |
| | | verify(chain).eq(eq(true), any(), eq(9)); |
| | | } |
| | | |
| | | @Test |
| | | @DisplayName("非公司账号查询公司列表时不追加本公司ID条件") |
| | | void platformAccountKeepsOriginalScope() { |
| | | when(companyAccountAccessService.getCurrentCompanyId()).thenReturn(null); |
| | | |
| | | AjaxResult<PageInfo<TGroupCompany>> result = controller.page(1, 10, null, null, null, null, null); |
| | | |
| | | assertNotNull(result); |
| | | verify(chain, never()).eq(eq(true), any(), any()); |
| | | } |
| | | } |
| | |
| | | import com.ruoyi.other.api.model.TCompanyRechargeRecord; |
| | | import com.ruoyi.other.mapper.TCompanyRechargeRecordMapper; |
| | | import com.ruoyi.other.mapper.TFleetMapper; |
| | | import com.ruoyi.system.api.domain.SysUser; |
| | | import com.ruoyi.system.api.feignClient.SysRoleClient; |
| | | import com.ruoyi.system.api.feignClient.SysUserClient; |
| | | import org.apache.ibatis.builder.MapperBuilderAssistant; |
| | |
| | | * 公司详情(车队数量、本月充值金额)统计逻辑单测,不依赖 Spring 容器与数据库。 |
| | | */ |
| | | class TGroupCompanyServiceImplTest { |
| | | |
| | | /** 编辑公司用例共享的远程账号客户端 mock,供 verify 使用 */ |
| | | private SysUserClient editUserClient; |
| | | |
| | | @BeforeAll |
| | | static void initTableInfo() { |
| | |
| | | return service; |
| | | } |
| | | |
| | | /** |
| | | * 编辑公司修改登录账号:按旧账号定位用户后把 userName 更新为新账号, |
| | | * 不误走补建分支(避免双账号残留) |
| | | */ |
| | | @Test |
| | | void editCompanyUpdatesUserNameWhenAccountChanged() { |
| | | TGroupCompany old = persistedCompany("company01"); |
| | | TGroupCompany edit = editCompanyBody(); |
| | | edit.setAccount("company02"); |
| | | |
| | | TGroupCompanyServiceImpl service = buildEditCompanyService(old, "company01", owner(100L), "company02", null); |
| | | |
| | | assertEquals(200, code(service.editCompany(edit))); |
| | | |
| | | ArgumentCaptor<SysUser> captor = ArgumentCaptor.forClass(SysUser.class); |
| | | Mockito.verify(editUserClient).updateSysUser(captor.capture()); |
| | | assertEquals("company02", captor.getValue().getUserName()); |
| | | Mockito.verify(editUserClient, Mockito.never()).addSysUser(Mockito.any()); |
| | | assertEquals("company02", edit.getAccount()); |
| | | } |
| | | |
| | | /** |
| | | * 编辑公司修改登录密码:调用 resetPassword 且仅携带 userId 与密码(前端已 HMAC-MD5) |
| | | */ |
| | | @Test |
| | | void editCompanyResetsPasswordWhenProvided() { |
| | | TGroupCompany old = persistedCompany("company01"); |
| | | TGroupCompany edit = editCompanyBody(); |
| | | edit.setAccount("company01"); |
| | | edit.setPassword("hmac-value"); |
| | | |
| | | TGroupCompanyServiceImpl service = buildEditCompanyService(old, "company01", owner(100L), null, null); |
| | | |
| | | assertEquals(200, code(service.editCompany(edit))); |
| | | |
| | | ArgumentCaptor<SysUser> pwdCaptor = ArgumentCaptor.forClass(SysUser.class); |
| | | Mockito.verify(editUserClient).resetPassword(pwdCaptor.capture()); |
| | | assertEquals(Long.valueOf(100L), pwdCaptor.getValue().getUserId()); |
| | | assertEquals("hmac-value", pwdCaptor.getValue().getPassword()); |
| | | |
| | | // 资料更新调用不携带密码,避免哈希/脱敏值写回 |
| | | ArgumentCaptor<SysUser> userCaptor = ArgumentCaptor.forClass(SysUser.class); |
| | | Mockito.verify(editUserClient).updateSysUser(userCaptor.capture()); |
| | | assertNull(userCaptor.getValue().getPassword()); |
| | | } |
| | | |
| | | /** |
| | | * 新账号已被其他用户占用:返回错误且不更新任何数据 |
| | | */ |
| | | @Test |
| | | void editCompanyRejectsAccountOccupiedByOtherUser() { |
| | | TGroupCompany old = persistedCompany("company01"); |
| | | TGroupCompany edit = editCompanyBody(); |
| | | edit.setAccount("company02"); |
| | | |
| | | TGroupCompanyServiceImpl service = buildEditCompanyService(old, "company01", owner(100L), |
| | | "company02", owner(999L)); |
| | | |
| | | assertEquals(500, code(service.editCompany(edit))); |
| | | |
| | | Mockito.verify(editUserClient, Mockito.never()).updateSysUser(Mockito.any()); |
| | | Mockito.verify(editUserClient, Mockito.never()).resetPassword(Mockito.any()); |
| | | Mockito.verify(service, Mockito.never()).updateById(Mockito.any()); |
| | | } |
| | | |
| | | /** |
| | | * 编辑时密码留空:不调用 resetPassword,密码保持不变 |
| | | */ |
| | | @Test |
| | | void editCompanyKeepsPasswordWhenAbsent() { |
| | | TGroupCompany old = persistedCompany("company01"); |
| | | TGroupCompany edit = editCompanyBody(); |
| | | edit.setAccount("company01"); |
| | | |
| | | TGroupCompanyServiceImpl service = buildEditCompanyService(old, "company01", owner(100L), null, null); |
| | | |
| | | assertEquals(200, code(service.editCompany(edit))); |
| | | |
| | | Mockito.verify(editUserClient, Mockito.never()).resetPassword(Mockito.any()); |
| | | Mockito.verify(editUserClient).updateSysUser(Mockito.any(SysUser.class)); |
| | | } |
| | | |
| | | private TGroupCompany persistedCompany(String account) { |
| | | TGroupCompany company = new TGroupCompany(); |
| | | company.setId(1); |
| | | company.setAccount(account); |
| | | company.setName("旧公司名"); |
| | | return company; |
| | | } |
| | | |
| | | private TGroupCompany editCompanyBody() { |
| | | TGroupCompany company = new TGroupCompany(); |
| | | company.setId(1); |
| | | company.setName("新公司名"); |
| | | company.setContactPhone("13800000000"); |
| | | return company; |
| | | } |
| | | |
| | | private SysUser owner(Long userId) { |
| | | SysUser user = new SysUser(); |
| | | user.setUserId(userId); |
| | | user.setPassword("hashed"); |
| | | return user; |
| | | } |
| | | |
| | | /** |
| | | * 构建编辑公司场景的 spy:getById 返回库里旧记录,updateById 打桩; |
| | | * queryUserByUserName 按账号返回不同用户(newAccountUser 非 null 时用于模拟新账号占用) |
| | | */ |
| | | private TGroupCompanyServiceImpl buildEditCompanyService(TGroupCompany old, String oldAccount, SysUser oldAccountUser, |
| | | String newAccount, SysUser newAccountUser) { |
| | | SysUserClient sysUserClient = Mockito.mock(SysUserClient.class); |
| | | Mockito.when(sysUserClient.queryUserByUserName(oldAccount)).thenReturn(R.ok(oldAccountUser)); |
| | | if (newAccount != null) { |
| | | Mockito.when(sysUserClient.queryUserByUserName(newAccount)).thenReturn(R.ok(newAccountUser)); |
| | | } |
| | | Mockito.when(sysUserClient.updateSysUser(Mockito.any())).thenReturn(R.ok(true)); |
| | | Mockito.when(sysUserClient.resetPassword(Mockito.any())).thenReturn(R.ok()); |
| | | this.editUserClient = sysUserClient; |
| | | |
| | | TGroupCompanyServiceImpl service = Mockito.spy(new TGroupCompanyServiceImpl()); |
| | | Mockito.doReturn(old).when(service).getById(Mockito.any()); |
| | | Mockito.doReturn(true).when(service).updateById(Mockito.any()); |
| | | Mockito.doReturn(0L).when(service).count(Mockito.any()); |
| | | ReflectionTestUtils.setField(service, "sysUserClient", sysUserClient); |
| | | return service; |
| | | } |
| | | |
| | | private int code(AjaxResult result) { |
| | | return ((Number) result.get("code")).intValue(); |
| | | } |
| New file |
| | |
| | | -- ============================================================================= |
| | | -- 2026-09-30 公司管理员菜单 path 与权限键(perms)对齐修复 |
| | | -- ----------------------------------------------------------------------------- |
| | | -- 背景:后端登录下发的权限集合取自 sys_menu.path(SysMenuMapper.selectMenuPermsByUserId |
| | | -- 查询的是 m.path),本项目约定「菜单 path = 前端 accessKey = 接口权限键」。 |
| | | -- 20260930_company_admin_role_menu.sql 早期版本把新建菜单的 path 写成 |
| | | -- '#'(按钮)、'list'(列表页)、'/card'(卡片管理目录),与权限键不一致,导致: |
| | | -- 1) 公司账号登录后 permissions 缺 /fleet/list、/vinManagement/list、 |
| | | -- /chargingCard/list 等键,车队/VIN/卡片菜单整块不显示(仅白名单可见); |
| | | -- 2) /other/group-company/page 等接口鉴权失败(提示“没有权限”)。 |
| | | -- 处理:按 perms 定位(不依赖 menu_id,跨环境安全),把相关菜单 path 改写为 perms。 |
| | | -- 说明:仅更新 sys_menu.path 一列,不改菜单绑定关系;语句幂等,可重复执行; |
| | | -- 与 20260930_company_admin_role_menu.sql 配合执行,两者先后顺序均可。 |
| | | -- ============================================================================= |
| | | USE charging_pile_other; |
| | | SET NAMES utf8mb4; |
| | | |
| | | -- 1. 车队管理(列表页 + 6 个按钮) |
| | | UPDATE sys_menu SET path = '/fleet/list' WHERE perms = '/fleet/list' AND (path IS NULL OR path <> '/fleet/list'); |
| | | UPDATE sys_menu SET path = '/fleet/add' WHERE perms = '/fleet/add' AND (path IS NULL OR path <> '/fleet/add'); |
| | | UPDATE sys_menu SET path = '/fleet/edit' WHERE perms = '/fleet/edit' AND (path IS NULL OR path <> '/fleet/edit'); |
| | | UPDATE sys_menu SET path = '/fleet/delete' WHERE perms = '/fleet/delete' AND (path IS NULL OR path <> '/fleet/delete'); |
| | | UPDATE sys_menu SET path = '/fleet/status' WHERE perms = '/fleet/status' AND (path IS NULL OR path <> '/fleet/status'); |
| | | UPDATE sys_menu SET path = '/fleet/import' WHERE perms = '/fleet/import' AND (path IS NULL OR path <> '/fleet/import'); |
| | | UPDATE sys_menu SET path = '/fleet/export' WHERE perms = '/fleet/export' AND (path IS NULL OR path <> '/fleet/export'); |
| | | |
| | | -- 2. VIN 码管理(列表页 + 8 个按钮) |
| | | UPDATE sys_menu SET path = '/vinManagement/list' WHERE perms = '/vinManagement/list' AND (path IS NULL OR path <> '/vinManagement/list'); |
| | | UPDATE sys_menu SET path = '/vinManagement/add' WHERE perms = '/vinManagement/add' AND (path IS NULL OR path <> '/vinManagement/add'); |
| | | UPDATE sys_menu SET path = '/vinManagement/edit' WHERE perms = '/vinManagement/edit' AND (path IS NULL OR path <> '/vinManagement/edit'); |
| | | UPDATE sys_menu SET path = '/vinManagement/delete' WHERE perms = '/vinManagement/delete' AND (path IS NULL OR path <> '/vinManagement/delete'); |
| | | UPDATE sys_menu SET path = '/vinManagement/status' WHERE perms = '/vinManagement/status' AND (path IS NULL OR path <> '/vinManagement/status'); |
| | | UPDATE sys_menu SET path = '/vinManagement/import' WHERE perms = '/vinManagement/import' AND (path IS NULL OR path <> '/vinManagement/import'); |
| | | UPDATE sys_menu SET path = '/vinManagement/export' WHERE perms = '/vinManagement/export' AND (path IS NULL OR path <> '/vinManagement/export'); |
| | | UPDATE sys_menu SET path = '/vinManagement/allocate' WHERE perms = '/vinManagement/allocate' AND (path IS NULL OR path <> '/vinManagement/allocate'); |
| | | UPDATE sys_menu SET path = '/vinManagement/unbind' WHERE perms = '/vinManagement/unbind' AND (path IS NULL OR path <> '/vinManagement/unbind'); |
| | | |
| | | -- 3. 卡片管理(目录与列表页 path 同为 /chargingCard/list;含 6+2+2+1 个按钮) |
| | | UPDATE sys_menu SET path = '/chargingCard/list' WHERE perms = '/chargingCard/list' AND (path IS NULL OR path <> '/chargingCard/list'); |
| | | UPDATE sys_menu SET path = '/chargingCard/add' WHERE perms = '/chargingCard/add' AND (path IS NULL OR path <> '/chargingCard/add'); |
| | | UPDATE sys_menu SET path = '/chargingCard/edit' WHERE perms = '/chargingCard/edit' AND (path IS NULL OR path <> '/chargingCard/edit'); |
| | | UPDATE sys_menu SET path = '/chargingCard/delete' WHERE perms = '/chargingCard/delete' AND (path IS NULL OR path <> '/chargingCard/delete'); |
| | | UPDATE sys_menu SET path = '/chargingCard/status' WHERE perms = '/chargingCard/status' AND (path IS NULL OR path <> '/chargingCard/status'); |
| | | UPDATE sys_menu SET path = '/chargingCard/import' WHERE perms = '/chargingCard/import' AND (path IS NULL OR path <> '/chargingCard/import'); |
| | | UPDATE sys_menu SET path = '/chargingCard/export' WHERE perms = '/chargingCard/export' AND (path IS NULL OR path <> '/chargingCard/export'); |
| | | UPDATE sys_menu SET path = '/chargingCard/allocate' WHERE perms = '/chargingCard/allocate' AND (path IS NULL OR path <> '/chargingCard/allocate'); |
| | | UPDATE sys_menu SET path = '/chargingCard/unbind' WHERE perms = '/chargingCard/unbind' AND (path IS NULL OR path <> '/chargingCard/unbind'); |
| | | |
| | | -- 4. 白名单用户(导出/启停/解绑/额度分配;其余按钮 path 与 perms 原已一致) |
| | | UPDATE sys_menu SET path = '/appUserWhitelist/export' WHERE perms = '/appUserWhitelist/export' AND (path IS NULL OR path <> '/appUserWhitelist/export'); |
| | | UPDATE sys_menu SET path = '/appUserWhitelist/status' WHERE perms = '/appUserWhitelist/status' AND (path IS NULL OR path <> '/appUserWhitelist/status'); |
| | | UPDATE sys_menu SET path = '/appUserWhitelist/unbind' WHERE perms = '/appUserWhitelist/unbind' AND (path IS NULL OR path <> '/appUserWhitelist/unbind'); |
| | | UPDATE sys_menu SET path = '/appUserWhitelist/allocate' WHERE perms = '/appUserWhitelist/allocate' AND (path IS NULL OR path <> '/appUserWhitelist/allocate'); |
| | | |
| | | -- 5. 卡片管理其余子页(刷卡充电订单、卡片收支明细):同批菜单同类修复,path 与权限键对齐 |
| | | -- (公司账号未绑定这两个子页,此处修复的是平台/其他角色绑定的历史菜单) |
| | | UPDATE sys_menu SET path = '/cardChargingOrder' WHERE perms = '/cardChargingOrder' AND (path IS NULL OR path <> '/cardChargingOrder'); |
| | | UPDATE sys_menu SET path = '/cardChargingOrder/detail' WHERE perms = '/cardChargingOrder/detail' AND (path IS NULL OR path <> '/cardChargingOrder/detail'); |
| | | UPDATE sys_menu SET path = '/chargingCardBalanceDetail/list' WHERE perms = '/chargingCardBalanceDetail/list' AND (path IS NULL OR path <> '/chargingCardBalanceDetail/list'); |
| | | UPDATE sys_menu SET path = '/chargingCardBalanceDetail/export' WHERE perms = '/chargingCardBalanceDetail/export' AND (path IS NULL OR path <> '/chargingCardBalanceDetail/export'); |
| | | |
| | | -- ============================================================================= |
| | | -- 回查语句(人工核对,不参与脚本执行) |
| | | -- ============================================================================= |
| | | -- R1. 期望 0:以上权限键对应菜单仍有 path <> perms 的记录 |
| | | -- SELECT COUNT(*) FROM sys_menu WHERE perms IS NOT NULL AND (path IS NULL OR path <> perms) |
| | | -- AND (perms LIKE '/fleet%' OR perms LIKE '/vinManagement%' |
| | | -- OR perms LIKE '/chargingCard%' OR perms LIKE '/appUserWhitelist%'); |
| | | -- R2. 公司账号权限集合(等价 selectMenuPermsByUserId,<userId> 替换为公司账号 user_id) |
| | | -- SELECT DISTINCT m.path FROM sys_menu m |
| | | -- JOIN sys_role_menu rm ON rm.menu_id = m.menu_id |
| | | -- JOIN sys_user_role ur ON ur.role_id = rm.role_id |
| | | -- JOIN sys_role r ON r.role_id = ur.role_id |
| | | -- WHERE m.status = '0' AND r.status = '0' AND ur.user_id = <userId> |
| | | -- ORDER BY m.path; |
| | | -- R3. 关键键命中(期望 7 行):/appUser、/appUserWhitelist/list、/chargingCard/list、 |
| | | -- /fleet、/fleet/list、/vinManagement、/vinManagement/list |
| | | -- SELECT m.path FROM sys_menu m |
| | | -- JOIN sys_role_menu rm ON rm.menu_id = m.menu_id |
| | | -- JOIN sys_user_role ur ON ur.role_id = rm.role_id |
| | | -- JOIN sys_role r ON r.role_id = ur.role_id |
| | | -- WHERE m.status = '0' AND r.status = '0' AND ur.user_id = <userId> |
| | | -- AND m.path IN ('/appUser','/appUserWhitelist/list','/chargingCard/list', |
| | | -- '/fleet','/fleet/list','/vinManagement','/vinManagement/list'); |
| | |
| | | -- 4) company_admin 默认菜单 = 车队管理 + VIN码管理 + 卡片管理(不含刷卡充电订单、 |
| | | -- 卡片收支明细子页)+ 白名单用户(含全部操作按钮),白名单挂载在「用户管理」下 |
| | | -- 5) 旧 company 角色不物理删除,迁移绑定后停用(status=1) |
| | | -- 6) 【重要】后端登录下发的权限集合取自 sys_menu.path(SysMenuMapper.selectMenuPermsByUserId |
| | | -- 查询的是 m.path),本项目约定「菜单 path = 前端 accessKey = 接口权限键」: |
| | | -- M 目录 path = 前端目录 accessKey(如 /fleet); |
| | | -- C 列表页 path = 前端页面 accessKey(如 /fleet/list); |
| | | -- F 按钮 path = 该按钮权限键(如 /fleet/add;沿用若依默认的 '#' 会导致权限键丢失)。 |
| | | -- 本脚本执行后请继续执行 ChargingPile/sql/20260930_company_admin_menu_path_fix.sql, |
| | | -- 修正历史菜单 path(列表页 'list'、按钮 '#'、卡片管理目录 '/card')。 |
| | | -- ============================================================================= |
| | | SET NAMES utf8mb4; |
| | | |
| | |
| | | |
| | | -- 1.2 车队列表(菜单 C) |
| | | INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) |
| | | SELECT '车队列表', m.menu_id, 1, 'list', 'fleet', NULL, 1, 0, 'C', '0', '0', '/fleet/list', 'list', 'admin', NOW(), '公司管理员默认菜单' |
| | | SELECT '车队列表', m.menu_id, 1, '/fleet/list', 'fleet', NULL, 1, 0, 'C', '0', '0', '/fleet/list', 'list', 'admin', NOW(), '公司管理员默认菜单' |
| | | FROM sys_menu m |
| | | WHERE m.menu_type = 'M' AND m.path = '/fleet' |
| | | AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_type = 'C' AND perms = '/fleet/list'); |