Forráskód Böngészése

移动端 子账号登录 切换账号

qinxuyang 2 hónapja
szülő
commit
033b44895a
17 módosított fájl, 759 hozzáadás és 5 törlés
  1. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreUserVo.java
  2. 52 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/SubAccountStoreListVo.java
  3. 10 0
      alien-entity/src/main/java/shop/alien/mapper/StorePlatformMenuMapper.java
  4. 24 0
      alien-entity/src/main/java/shop/alien/mapper/SubAccountStoreMapper.java
  5. 41 0
      alien-entity/src/main/resources/mapper/StorePlatformMenuMapper.xml
  6. 65 0
      alien-entity/src/main/resources/mapper/SubAccountStoreMapper.xml
  7. 15 0
      alien-gateway/src/main/java/shop/alien/gateway/controller/StoreUserController.java
  8. 15 0
      alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformRoleGatewayMapper.java
  9. 26 0
      alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformRoleMenuGatewayMapper.java
  10. 15 0
      alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformUserRoleGatewayMapper.java
  11. 8 4
      alien-gateway/src/main/java/shop/alien/gateway/service/StoreUserService.java
  12. 167 1
      alien-gateway/src/main/java/shop/alien/gateway/service/impl/StoreUserServiceImpl.java
  13. 50 0
      alien-store/src/main/java/shop/alien/store/controller/StoreUserController.java
  14. 21 0
      alien-store/src/main/java/shop/alien/store/service/RoleMenuService.java
  15. 20 0
      alien-store/src/main/java/shop/alien/store/service/SubAccountStoreService.java
  16. 189 0
      alien-store/src/main/java/shop/alien/store/service/impl/RoleMenuServiceImpl.java
  17. 37 0
      alien-store/src/main/java/shop/alien/store/service/impl/SubAccountStoreServiceImpl.java

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreUserVo.java

@@ -56,6 +56,10 @@ public class StoreUserVo extends StoreUser {
     @ApiModelProperty(value = "主账号手机号")
     private String parentAccountPhone;
 
+    @ApiModelProperty(value = "角色ID(当前账号在目标门店的角色)")
+    private Long roleId;
 
+    @ApiModelProperty(value = "角色名称(当前账号在目标门店的角色名称)")
+    private String roleName;
 
 }

+ 52 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/SubAccountStoreListVo.java

@@ -0,0 +1,52 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 子账号关联门店列表 VO
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Data
+@JsonInclude(JsonInclude.Include.ALWAYS)
+@ApiModel(value = "SubAccountStoreListVo对象", description = "子账号关联门店列表")
+public class SubAccountStoreListVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "门店ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "门店名称")
+    private String storeName;
+
+    @ApiModelProperty(value = "门店地址")
+    private String storeAddress;
+
+    @ApiModelProperty(value = "门店电话")
+    private String storeTel;
+
+    @ApiModelProperty(value = "营业状态(-1:注销中, 0:正常营业, 1:暂停营业, 2:筹建中, 99:永久关门)")
+    private Integer businessStatus;
+
+    @ApiModelProperty(value = "角色ID")
+    private Long roleId;
+
+    @ApiModelProperty(value = "角色名称")
+    private String roleName;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "账号名称")
+    private String accountName;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+}

+ 10 - 0
alien-entity/src/main/java/shop/alien/mapper/StorePlatformMenuMapper.java

@@ -2,8 +2,11 @@ package shop.alien.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 import shop.alien.entity.store.StorePlatformMenu;
 
+import java.util.List;
+
 /**
  * 商家PC菜单权限表 Mapper 接口
  *
@@ -13,5 +16,12 @@ import shop.alien.entity.store.StorePlatformMenu;
 @Mapper
 public interface StorePlatformMenuMapper extends BaseMapper<StorePlatformMenu> {
 
+    /**
+     * 根据角色ID查询权限菜单列表
+     *
+     * @param roleId 角色ID
+     * @return 菜单列表
+     */
+    List<StorePlatformMenu> selectMenusByRoleId(@Param("roleId") Long roleId);
 }
 

+ 24 - 0
alien-entity/src/main/java/shop/alien/mapper/SubAccountStoreMapper.java

@@ -0,0 +1,24 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.vo.SubAccountStoreListVo;
+
+import java.util.List;
+
+/**
+ * 子账号关联门店 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Mapper
+public interface SubAccountStoreMapper extends BaseMapper<Object> {
+    /**
+     * 根据用户ID查询子账号关联的门店列表
+     * @param userId 用户ID(store_user.id)
+     * @return 门店列表
+     */
+    List<SubAccountStoreListVo> selectSubAccountStoreListByUserId(@Param("userId") Integer userId);
+}

+ 41 - 0
alien-entity/src/main/resources/mapper/StorePlatformMenuMapper.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.StorePlatformMenuMapper">
+
+    <!-- 根据角色ID查询权限菜单列表 -->
+    <select id="selectMenusByRoleId" resultType="shop.alien.entity.store.StorePlatformMenu">
+        SELECT
+            m.menu_id AS menuId,
+            m.menu_name AS menuName,
+            m.parent_id AS parentId,
+            m.menu_type AS menuType,
+            m.menu_sort AS menuSort,
+            m.level AS level,
+            m.path AS path,
+            m.component AS component,
+            m.perms AS perms,
+            m.icon AS icon,
+            m.status AS status,
+            m.visible AS visible,
+            m.is_frame AS isFrame,
+            m.is_cache AS isCache,
+            m.del_flag AS delFlag,
+            m.create_by AS createBy,
+            m.created_time AS createdTime,
+            m.update_by AS updateBy,
+            m.updated_time AS updatedTime,
+            m.remark AS remark
+        FROM
+            store_platform_menu m
+        INNER JOIN store_platform_role_menu rm ON m.menu_id = rm.menu_id
+        WHERE
+            rm.role_id = #{roleId}
+            AND m.del_flag = '0'
+            AND m.status = '0'
+        ORDER BY
+            m.menu_sort ASC
+    </select>
+
+</mapper>

+ 65 - 0
alien-entity/src/main/resources/mapper/SubAccountStoreMapper.xml

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.SubAccountStoreMapper">
+
+    <!-- 根据用户ID查询子账号关联的门店列表(包含主账号的门店) -->
+    <select id="selectSubAccountStoreListByUserId" resultType="shop.alien.entity.store.vo.SubAccountStoreListVo">
+        -- 查询子账号通过store_platform_user_role关联的门店
+        SELECT
+            si.id AS storeId,
+            si.store_name COLLATE utf8mb4_unicode_ci AS storeName,
+            si.store_address COLLATE utf8mb4_unicode_ci AS storeAddress,
+            si.store_tel COLLATE utf8mb4_unicode_ci AS storeTel,
+            si.business_status AS businessStatus,
+            spur.role_id AS roleId,
+            spr.role_name COLLATE utf8mb4_unicode_ci AS roleName,
+            spur.user_id AS userId,
+            spur.account_name COLLATE utf8mb4_unicode_ci AS accountName,
+            su.phone COLLATE utf8mb4_unicode_ci AS phone
+        FROM
+            store_platform_user_role spur
+        INNER JOIN store_user su ON spur.user_id = su.id
+        INNER JOIN store_info si ON spur.store_id = si.id
+        LEFT JOIN store_platform_role spr ON spur.role_id = spr.role_id 
+            AND (spr.del_flag = '0' OR spr.del_flag IS NULL)
+        WHERE
+            spur.user_id = #{userId}
+            AND spur.delete_flag = 0
+            AND su.delete_flag = 0
+            AND si.delete_flag = 0
+        
+        UNION
+        
+        -- 查询主账号的门店(通过store_user.store_id关联)
+        SELECT
+            si_main.id AS storeId,
+            si_main.store_name COLLATE utf8mb4_unicode_ci AS storeName,
+            si_main.store_address COLLATE utf8mb4_unicode_ci AS storeAddress,
+            si_main.store_tel COLLATE utf8mb4_unicode_ci AS storeTel,
+            si_main.business_status AS businessStatus,
+            NULL AS roleId,
+            NULL AS roleName,
+            su_main.id AS userId,
+            su_main.name COLLATE utf8mb4_unicode_ci AS accountName,
+            su_main.phone COLLATE utf8mb4_unicode_ci AS phone
+        FROM
+            store_user su_main
+        INNER JOIN store_info si_main ON su_main.store_id = si_main.id
+        WHERE
+            (
+                -- 如果传入的是子账号,查询其主账号的门店
+                (su_main.id = (SELECT sub_account_id FROM store_user WHERE id = #{userId} AND account_type = 2 AND delete_flag = 0 LIMIT 1))
+                OR
+                -- 如果传入的是主账号,查询自己的门店
+                (su_main.id = #{userId} AND su_main.account_type = 1)
+            )
+            AND su_main.delete_flag = 0
+            AND si_main.delete_flag = 0
+            AND su_main.store_id IS NOT NULL
+        
+        ORDER BY storeId DESC
+    </select>
+
+</mapper>

+ 15 - 0
alien-gateway/src/main/java/shop/alien/gateway/controller/StoreUserController.java

@@ -87,4 +87,19 @@ public class StoreUserController {
                 : R.fail("密码错误");
     }
 
+    /**
+     * 切换子账号门店(类似登录接口,无需密码和验证码)
+     */
+    @ApiOperation(value = "切换子账号门店", notes = "切换子账号当前操作的门店,相当于无密码无验证码的登录接口,返回用户信息和token,包含角色id")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID(store_user.id)", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeId", value = "目标门店ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/switchSubAccountStore")
+    public R<StoreUserVo> switchSubAccountStore(@RequestParam("userId") Integer userId, @RequestParam("storeId") Integer storeId) {
+        log.info("StoreUserController.switchSubAccountStore?userId={}, storeId={}", userId, storeId);
+        return storeUserService.switchSubAccountStore(userId, storeId);
+    }
+
 }

+ 15 - 0
alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformRoleGatewayMapper.java

@@ -0,0 +1,15 @@
+package shop.alien.gateway.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StorePlatformRole;
+
+/**
+ * 平台角色信息 Mapper 接口(Gateway)
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Mapper
+public interface StorePlatformRoleGatewayMapper extends BaseMapper<StorePlatformRole> {
+}

+ 26 - 0
alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformRoleMenuGatewayMapper.java

@@ -0,0 +1,26 @@
+package shop.alien.gateway.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import shop.alien.entity.store.StorePlatformRoleMenu;
+
+/**
+ * 角色菜单关联表 Mapper 接口(Gateway)
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Mapper
+public interface StorePlatformRoleMenuGatewayMapper extends BaseMapper<StorePlatformRoleMenu> {
+    
+    /**
+     * 根据角色ID查询菜单权限数量
+     *
+     * @param roleId 角色ID
+     * @return 菜单权限数量
+     */
+    @Select("SELECT COUNT(*) FROM store_platform_role_menu WHERE role_id = #{roleId}")
+    Integer countMenuByRoleId(@Param("roleId") Long roleId);
+}

+ 15 - 0
alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformUserRoleGatewayMapper.java

@@ -0,0 +1,15 @@
+package shop.alien.gateway.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StorePlatformUserRole;
+
+/**
+ * 用户角色关联表 Mapper 接口(Gateway)
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Mapper
+public interface StorePlatformUserRoleGatewayMapper extends BaseMapper<StorePlatformUserRole> {
+}

+ 8 - 4
alien-gateway/src/main/java/shop/alien/gateway/service/StoreUserService.java

@@ -1,14 +1,10 @@
 package shop.alien.gateway.service;
 
-import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StoreUserVo;
 
-import java.io.IOException;
-import java.util.List;
-
 /**
  * 二期-门店用户 服务类
  *
@@ -23,4 +19,12 @@ public interface StoreUserService extends IService<StoreUser> {
      */
     R<StoreUserVo> createToKen(StoreUser storeUser);
 
+    /**
+     * 切换子账号门店(类似登录接口,无需密码和验证码)
+     * @param userId 用户ID(store_user.id)
+     * @param storeId 目标门店ID
+     * @return 用户信息和token
+     */
+    R<StoreUserVo> switchSubAccountStore(Integer userId, Integer storeId);
+
 }

+ 167 - 1
alien-gateway/src/main/java/shop/alien/gateway/service/impl/StoreUserServiceImpl.java

@@ -1,23 +1,31 @@
 package shop.alien.gateway.service.impl;
 
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.store.StorePlatformRole;
+import shop.alien.entity.store.StorePlatformUserRole;
 import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StoreUserVo;
 import shop.alien.gateway.config.BaseRedisService;
 import shop.alien.gateway.mapper.StoreInfoGatewayMapper;
+import shop.alien.gateway.mapper.StorePlatformRoleGatewayMapper;
+import shop.alien.gateway.mapper.StorePlatformRoleMenuGatewayMapper;
+import shop.alien.gateway.mapper.StorePlatformUserRoleGatewayMapper;
 import shop.alien.gateway.mapper.StoreUserGatewayMapper;
 import shop.alien.gateway.service.StoreUserService;
 import shop.alien.util.common.JwtUtil;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -26,6 +34,7 @@ import java.util.Map;
  * @author ssk
  * @since 2024-12-11
  */
+@Slf4j
 @Transactional
 @Service
 @RequiredArgsConstructor
@@ -42,6 +51,12 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
 
     private final BaseRedisService baseRedisService;
 
+    private final StorePlatformUserRoleGatewayMapper storePlatformUserRoleMapper;
+    
+    private final StorePlatformRoleMenuGatewayMapper storePlatformRoleMenuMapper;
+    
+    private final StorePlatformRoleGatewayMapper storePlatformRoleMapper;
+
     /**
      * token
      *
@@ -81,7 +96,67 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
         tokenMap.put("userType", "store");
         storeUserVo.setToken(JwtUtil.createJWT("store_" + storeUser.getPhone(), storeUser.getName(), JSONObject.toJSONString(tokenMap), effectiveTimeIntLong));
         baseRedisService.setString("store_" + storeUser.getPhone(), storeUserVo.getToken());
-        StoreInfo storeInfo = storeInfoMapper.selectById(storeUser.getStoreId());
+        if(storeUserVo.getStoreId() == null){
+            // 子账号登录,查询当前子账号权限最多的那个门店和角色id
+            log.info("子账号登录,storeId为空,查询权限最多的门店 - userId: {}", storeUser.getId());
+            
+            // 查询子账号关联的所有门店和角色
+            LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
+            roleWrapper.eq(StorePlatformUserRole::getUserId, storeUser.getId())
+                    .eq(StorePlatformUserRole::getDeleteFlag, 0);
+            List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
+            
+            if (userRoles != null && !userRoles.isEmpty()) {
+                // 计算每个角色的菜单权限数量,选择权限最多的门店
+                StorePlatformUserRole maxPermissionRole = null;
+                int maxPermissionCount = -1;
+                
+                for (StorePlatformUserRole userRole : userRoles) {
+                    if (userRole.getRoleId() != null) {
+                        // 查询该角色的菜单权限数量
+                        Integer permissionCount = storePlatformRoleMenuMapper.countMenuByRoleId(userRole.getRoleId());
+                        if (permissionCount == null) {
+                            permissionCount = 0;
+                        }
+                        
+                        // 如果权限数量更多,或者权限数量相同但角色ID更大,则更新
+                        if (permissionCount > maxPermissionCount || 
+                            (permissionCount == maxPermissionCount && 
+                             (maxPermissionRole == null || userRole.getRoleId() > maxPermissionRole.getRoleId()))) {
+                            maxPermissionCount = permissionCount;
+                            maxPermissionRole = userRole;
+                        }
+                    }
+                }
+                
+                if (maxPermissionRole != null) {
+                    // 设置门店ID和角色ID
+                    storeUserVo.setStoreId(maxPermissionRole.getStoreId());
+                    storeUserVo.setRoleId(maxPermissionRole.getRoleId());
+                    
+                    // 查询角色名称
+                    if (maxPermissionRole.getRoleId() != null) {
+                        StorePlatformRole role = storePlatformRoleMapper.selectById(maxPermissionRole.getRoleId());
+                        if (role != null) {
+                            storeUserVo.setRoleName(role.getRoleName());
+                        }
+                    }
+                    
+                    log.info("子账号权限最多的门店查询成功 - userId: {}, storeId: {}, roleId: {}, roleName: {}, 权限数量: {}", 
+                            storeUser.getId(), maxPermissionRole.getStoreId(), maxPermissionRole.getRoleId(), 
+                            storeUserVo.getRoleName(), maxPermissionCount);
+                } else {
+                    log.warn("子账号关联的门店都没有角色ID - userId: {}", storeUser.getId());
+                }
+            } else {
+                log.warn("子账号未关联任何门店 - userId: {}", storeUser.getId());
+            }
+        }
+        // 查询门店信息(如果storeId不为空)
+        StoreInfo storeInfo = null;
+        if (storeUserVo.getStoreId() != null) {
+            storeInfo = storeInfoMapper.selectById(storeUserVo.getStoreId());
+        }
         if (storeInfo != null) {
             storeUserVo.setBusinessSection(storeInfo.getBusinessSection());
             storeUserVo.setBusinessTypesName(storeInfo.getBusinessTypesName());
@@ -89,4 +164,95 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
         return R.data(storeUserVo);
     }
 
+    @Override
+    public R<StoreUserVo> switchSubAccountStore(Integer userId, Integer storeId) {
+        log.info("StoreUserServiceImpl.switchSubAccountStore?userId={}, storeId={}", userId, storeId);
+
+        if (userId == null || storeId == null) {
+            log.warn("用户ID或门店ID为空 - userId: {}, storeId: {}", userId, storeId);
+            return R.fail("用户ID或门店ID不能为空");
+        }
+
+        // 1. 查询用户信息(类似登录接口,验证用户是否存在)
+        StoreUser storeUser = this.getById(userId);
+        if (storeUser == null) {
+            log.warn("用户不存在 - userId: {}", userId);
+            return R.fail("当前账号不存在,请先去注册账号!");
+        }
+
+        // 2. 验证账号状态
+        if (storeUser.getStatus() == 1) {
+            log.warn("账号被禁用 - userId: {}", userId);
+            return R.fail("账号被禁用");
+        }
+
+        // 3. 判断是主账号还是子账号,并验证门店关联
+        Long roleId = null;
+        Integer finalStoreId = null; // 最终要回显的门店ID
+        StoreInfo storeInfo = new StoreInfo();
+        if (storeUser.getStoreId() != null && storeUser.getStoreId().equals(storeId)) {
+            // 主账号:直接回显 store_user 的门店 id
+//            if (storeUser.getStoreId() == null || !storeUser.getStoreId().equals(storeId)) {
+//                log.warn("主账号门店ID不匹配 - userId: {}, 传入storeId: {}, 实际storeId: {}",
+//                        userId, storeId, storeUser.getStoreId());
+//                return R.fail("切换失败,门店ID不匹配");
+//            }
+            finalStoreId = storeUser.getStoreId();
+            roleId = null; // 主账号没有角色id
+        } else {
+            // 子账号:查询角色关联表,回显子账号关联的门店 id
+            LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
+            roleWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                    .eq(StorePlatformUserRole::getStoreId, storeId)
+                    .eq(StorePlatformUserRole::getDeleteFlag, 0);
+            StorePlatformUserRole userRole = storePlatformUserRoleMapper.selectOne(roleWrapper);
+            
+            if (userRole == null) {
+                log.warn("子账号未关联目标门店 - userId: {}, storeId: {}", userId, storeId);
+                return R.fail("切换失败,请确认子账号是否关联了该门店");
+            }
+            
+            finalStoreId = userRole.getStoreId(); // 从关联表获取门店ID
+            storeInfo = storeInfoMapper.selectById(finalStoreId);
+            roleId = userRole.getRoleId() != null ? userRole.getRoleId() : null;
+        }
+
+        // 4. 删除旧的token(参考switchingStates逻辑)
+        baseRedisService.delete("store_" + storeUser.getPhone());
+        baseRedisService.delete("storePlatform_" + storeUser.getPhone());
+        log.info("删除用户token - phone: {}", storeUser.getPhone());
+
+        // 5. 创建临时用户对象用于生成token(设置正确的门店ID)
+        StoreUser tempUser = new StoreUser();
+        BeanUtils.copyProperties(storeUser, tempUser);
+        tempUser.setStoreId(finalStoreId);
+
+        // 6. 生成token(类似登录接口)
+        R<StoreUserVo> tokenResult = createToKen(tempUser);
+        if (tokenResult.getCode() != 200 || tokenResult.getData() == null) {
+            log.error("生成token失败 - userId: {}", userId);
+            return R.fail("切换失败,token生成失败");
+        }
+
+        // 7. 设置角色ID和门店ID(如果没有角色id,该字段为null)
+        StoreUserVo storeUserVo = tokenResult.getData();
+        storeUserVo.setRoleId(roleId);
+        storeUserVo.setStoreId(finalStoreId); // 确保回显正确的门店ID
+        
+        // 查询并设置角色名称
+        if (roleId != null) {
+            StorePlatformRole role = storePlatformRoleMapper.selectById(roleId);
+            if (role != null) {
+                storeUserVo.setRoleName(role.getRoleName());
+            }
+        }
+        
+        if(storeInfo != null){
+            storeUserVo.setName(storeInfo.getStoreName());
+        }
+
+        log.info("切换门店成功 - userId: {}, storeId: {}, roleId: {}", userId, finalStoreId, roleId);
+        return R.data(storeUserVo);
+    }
+
 }

+ 50 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreUserController.java

@@ -10,16 +10,21 @@ import org.apache.commons.lang3.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StorePlatformMenu;
 import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StoreInfoVo;
 import shop.alien.entity.store.vo.StoreUserVo;
+import shop.alien.entity.store.vo.SubAccountStoreListVo;
 import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreUserMapper;
 import shop.alien.store.config.BaseRedisService;
+import shop.alien.store.service.RoleMenuService;
 import shop.alien.store.service.StoreInfoService;
 import shop.alien.store.service.StoreUserService;
+import shop.alien.store.service.SubAccountStoreService;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -47,6 +52,10 @@ public class StoreUserController {
 
     private final BaseRedisService baseRedisService;
 
+    private final SubAccountStoreService subAccountStoreService;
+    
+    private final RoleMenuService roleMenuService;
+
     /**
      * 校验商户端账号是否禁用
      *
@@ -441,4 +450,45 @@ public class StoreUserController {
         lambdaUpdateWrapper.eq(StoreUser::getId, storeUserVo.getId());
         return R.data(storeUserMapper.update(null, lambdaUpdateWrapper));
     }
+
+    /**
+     * 子账号关联门店列表
+     */
+    @ApiOperation(value = "子账号关联门店列表", notes = "根据用户ID查询子账号关联的门店列表")
+    @ApiOperationSupport(order = 10)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID(store_user.id)", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getSubAccountStoreList")
+    public R<List<SubAccountStoreListVo>> getSubAccountStoreList(@RequestParam("userId") Integer userId) {
+        log.info("StoreUserController.getSubAccountStoreList?userId={}", userId);
+        List<SubAccountStoreListVo> storeList = subAccountStoreService.getSubAccountStoreList(userId);
+        return R.data(storeList);
+    }
+
+    /**
+     * 通过角色ID查询权限菜单
+     */
+    @ApiOperation(value = "通过角色ID查询权限菜单", notes = "根据角色ID查询该角色拥有的所有权限菜单")
+    @ApiOperationSupport(order = 11)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @GetMapping("/getMenusByRoleId")
+    public R<List<StorePlatformMenu>> getMenusByRoleId(@RequestParam("roleId") Long roleId) {
+        log.info("StoreUserController.getMenusByRoleId?roleId={}", roleId);
+        
+        if (roleId == null) {
+            log.warn("角色ID为空");
+            return R.fail("角色ID不能为空");
+        }
+        
+        try {
+            List<StorePlatformMenu> menus = roleMenuService.getMenusByRoleId(roleId);
+            return R.data(menus);
+        } catch (Exception e) {
+            log.error("查询角色权限菜单失败 - roleId: {}", roleId, e);
+            return R.fail("查询菜单失败:" + e.getMessage());
+        }
+    }
 }

+ 21 - 0
alien-store/src/main/java/shop/alien/store/service/RoleMenuService.java

@@ -0,0 +1,21 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.store.StorePlatformMenu;
+
+import java.util.List;
+
+/**
+ * 角色菜单服务接口
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+public interface RoleMenuService {
+    /**
+     * 通过角色ID查询权限菜单
+     *
+     * @param roleId 角色ID
+     * @return 菜单列表
+     */
+    List<StorePlatformMenu> getMenusByRoleId(Long roleId);
+}

+ 20 - 0
alien-store/src/main/java/shop/alien/store/service/SubAccountStoreService.java

@@ -0,0 +1,20 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.store.vo.SubAccountStoreListVo;
+
+import java.util.List;
+
+/**
+ * 子账号关联门店服务接口
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+public interface SubAccountStoreService {
+    /**
+     * 根据用户ID查询子账号关联的门店列表
+     * @param userId 用户ID(store_user.id)
+     * @return 门店列表
+     */
+    List<SubAccountStoreListVo> getSubAccountStoreList(Integer userId);
+}

+ 189 - 0
alien-store/src/main/java/shop/alien/store/service/impl/RoleMenuServiceImpl.java

@@ -0,0 +1,189 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.StorePlatformMenu;
+import shop.alien.mapper.StorePlatformMenuMapper;
+import shop.alien.store.service.RoleMenuService;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 角色菜单服务实现类
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class RoleMenuServiceImpl implements RoleMenuService {
+
+    private final StorePlatformMenuMapper storePlatformMenuMapper;
+
+    @Override
+    public List<StorePlatformMenu> getMenusByRoleId(Long roleId) {
+        log.info("RoleMenuServiceImpl.getMenusByRoleId?roleId={}", roleId);
+        
+        if (roleId == null) {
+            log.warn("角色ID为空");
+            return new ArrayList<>();
+        }
+        
+        try {
+            // 1. 查询角色直接关联的菜单列表
+            List<StorePlatformMenu> directMenus = storePlatformMenuMapper.selectMenusByRoleId(roleId);
+            
+            if (directMenus == null || directMenus.isEmpty()) {
+                log.info("角色未分配任何菜单权限 - roleId: {}", roleId);
+                return new ArrayList<>();
+            }
+            
+            // 2. 找出最高层级
+            Integer maxLevel = directMenus.stream()
+                    .map(StorePlatformMenu::getLevel)
+                    .filter(Objects::nonNull)
+                    .max(Integer::compareTo)
+                    .orElse(1);
+            
+            // 3. 收集所有需要的菜单ID(包括父菜单)
+            Set<Long> allMenuIds = directMenus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+            
+            // 4. 如果需要,补充父级权限
+            if (maxLevel >= 3) {
+                // 如果有三级权限,需要补充一级和二级父权限
+                for (StorePlatformMenu menu : directMenus) {
+                    if (menu.getLevel() != null && menu.getLevel() == 3) {
+                        // 补充二级父权限
+                        if (menu.getParentId() != null && menu.getParentId() != 0) {
+                            allMenuIds.add(menu.getParentId());
+                            // 补充一级父权限
+                            StorePlatformMenu level2Menu = storePlatformMenuMapper.selectById(menu.getParentId());
+                            if (level2Menu != null && level2Menu.getParentId() != null && level2Menu.getParentId() != 0) {
+                                allMenuIds.add(level2Menu.getParentId());
+                            }
+                        }
+                    }
+                }
+            } else if (maxLevel == 2) {
+                // 如果只有二级权限,需要补充一级父权限
+                for (StorePlatformMenu menu : directMenus) {
+                    if (menu.getLevel() != null && menu.getLevel() == 2) {
+                        if (menu.getParentId() != null && menu.getParentId() != 0) {
+                            allMenuIds.add(menu.getParentId());
+                        }
+                    }
+                }
+            }
+            
+            // 5. 查询所有需要的菜单(包括父菜单)
+            List<StorePlatformMenu> allMenus = new ArrayList<>();
+            if (!allMenuIds.isEmpty()) {
+                LambdaQueryWrapper<StorePlatformMenu> menuWrapper = new LambdaQueryWrapper<>();
+                menuWrapper.in(StorePlatformMenu::getMenuId, allMenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .eq(StorePlatformMenu::getStatus, "0")
+                        .orderByAsc(StorePlatformMenu::getMenuSort);
+                allMenus = storePlatformMenuMapper.selectList(menuWrapper);
+            }
+            
+            // 6. 构建树形结构
+            List<StorePlatformMenu> treeMenus = buildMenuTree(allMenus);
+            
+            log.info("查询角色权限菜单成功 - roleId: {}, 菜单数量: {}, 树形结构数量: {}", 
+                    roleId, allMenus.size(), treeMenus.size());
+            return treeMenus;
+            
+        } catch (Exception e) {
+            log.error("查询角色权限菜单失败 - roleId: {}", roleId, e);
+            return new ArrayList<>();
+        }
+    }
+    
+    /**
+     * 构建菜单树形结构
+     *
+     * @param menus 菜单列表
+     * @return 树形结构的菜单列表
+     */
+    private List<StorePlatformMenu> buildMenuTree(List<StorePlatformMenu> menus) {
+        if (menus == null || menus.isEmpty()) {
+            return new ArrayList<>();
+        }
+        
+        // 按层级和排序顺序排序
+        menus.sort(Comparator
+                .comparing(StorePlatformMenu::getLevel, Comparator.nullsLast(Integer::compareTo))
+                .thenComparing(StorePlatformMenu::getMenuSort, Comparator.nullsLast(Integer::compareTo)));
+        
+        // 创建菜单映射表(menuId -> menu)
+        Map<Long, StorePlatformMenu> menuMap = menus.stream()
+                .collect(Collectors.toMap(
+                        StorePlatformMenu::getMenuId,
+                        menu -> {
+                            // 初始化 children 列表
+                            if (menu.getChildren() == null) {
+                                menu.setChildren(new ArrayList<>());
+                            }
+                            return menu;
+                        },
+                        (existing, replacement) -> existing
+                ));
+        
+        // 构建树形结构
+        List<StorePlatformMenu> rootMenus = new ArrayList<>();
+        for (StorePlatformMenu menu : menus) {
+            Long parentId = menu.getParentId();
+            
+            if (parentId == null || parentId == 0) {
+                // 根菜单(一级菜单)
+                rootMenus.add(menu);
+            } else {
+                // 子菜单,添加到父菜单的 children 中
+                StorePlatformMenu parentMenu = menuMap.get(parentId);
+                if (parentMenu != null) {
+                    if (parentMenu.getChildren() == null) {
+                        parentMenu.setChildren(new ArrayList<>());
+                    }
+                    parentMenu.getChildren().add(menu);
+                } else {
+                    // 如果找不到父菜单,作为根菜单处理
+                    rootMenus.add(menu);
+                }
+            }
+        }
+        
+        // 对每个层级的 children 进行排序
+        sortMenuChildren(rootMenus);
+        
+        return rootMenus;
+    }
+    
+    /**
+     * 递归排序菜单的 children
+     *
+     * @param menus 菜单列表
+     */
+    private void sortMenuChildren(List<StorePlatformMenu> menus) {
+        if (menus == null || menus.isEmpty()) {
+            return;
+        }
+        
+        // 对当前层级排序
+        menus.sort(Comparator
+                .comparing(StorePlatformMenu::getMenuSort, Comparator.nullsLast(Integer::compareTo)));
+        
+        // 递归排序子菜单
+        for (StorePlatformMenu menu : menus) {
+            if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
+                sortMenuChildren(menu.getChildren());
+            }
+        }
+    }
+}

+ 37 - 0
alien-store/src/main/java/shop/alien/store/service/impl/SubAccountStoreServiceImpl.java

@@ -0,0 +1,37 @@
+package shop.alien.store.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.vo.SubAccountStoreListVo;
+import shop.alien.mapper.SubAccountStoreMapper;
+import shop.alien.store.service.SubAccountStoreService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 子账号关联门店服务实现类
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class SubAccountStoreServiceImpl implements SubAccountStoreService {
+
+    private final SubAccountStoreMapper subAccountStoreMapper;
+
+    @Override
+    public List<SubAccountStoreListVo> getSubAccountStoreList(Integer userId) {
+        log.info("SubAccountStoreServiceImpl.getSubAccountStoreList?userId={}", userId);
+        if (userId == null) {
+            log.warn("用户ID为空");
+            return new ArrayList<>();
+        }
+        List<SubAccountStoreListVo> storeList = subAccountStoreMapper.selectSubAccountStoreListByUserId(userId);
+        log.info("查询子账号关联门店列表完成 - 用户ID: {}, 门店数量: {}", userId, storeList != null ? storeList.size() : 0);
+        return storeList;
+    }
+}