多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档

  1. 修改用户表:sys_user 增加 tenant_id(租户ID,注意实体增加对应字段)

  2. 修改 MybatisPlusSaasConfig
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第1张图片
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第2张图片

  3. 修改登录接口 LoginController ,用户查询 ,登录检查用户是否有效不添加租户的附加条件(租户ID)
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第3张图片
    接口ISysUserService新增:

	/**
	 * 用户登录查询,不进行SQL解析,不添加租户的附加条件
	 * @param userName
	 * @return
	 */
	SysUser getOneUser(String userName);

实现类SysUserServiceImpl新增:

	/**
	 * 用户登录查询,不进行SQL解析,不添加租户的附加条件
	 * @param userName
	 * @return
	 */
	@Override
	public SysUser getOneUser(String userName) {
		return userMapper.getOneUser(userName);
	}

修改SysUserMapper增加:

	/**
	 * 用户登录查询,不进行SQL解析,不添加租户的附加条件
	 * @param userName
	 * @return
	 */
	@SqlParser(filter = true)
	SysUser getOneUser(String userName);

修改SysUserMapper.xml 增加:

	<!--用户登录查询,不进行SQL解析,不添加租户的附加条件-->
	<select id="getOneUser" parameterType="string" resultType="org.jeecg.modules.system.entity.SysUser">
		select * from  sys_user  where username = #{username} and del_flag = 0
	</select>

修改SysUserController 新增用户方法,新增用户时,将当前登录用户的tenantId值赋给新用户

@RequestMapping(value = "/add", method = RequestMethod.POST)
	public Result<SysUser> add(@RequestBody JSONObject jsonObject,HttpServletRequest req) {
		Result<SysUser> result = new Result<SysUser>();
		String selectedRoles = jsonObject.getString("selectedroles");
		String selectedDeparts = jsonObject.getString("selecteddeparts");
		try {
            String tenantId = req.getHeader("tenant-id");
			SysUser user = JSON.parseObject(jsonObject.toJSONString(), SysUser.class);
			user.setCreateTime(new Date());//设置创建时间
			String salt = oConvertUtils.randomGen(8);
			user.setSalt(salt);
			String passwordEncode = PasswordUtil.encrypt(user.getUsername(), user.getPassword(), salt);
			user.setPassword(passwordEncode);
			user.setStatus(1);
			user.setDelFlag(CommonConstant.DEL_FLAG_0);

            user.setRelTenantIds(tenantId);
			user.setTenantId(Integer.valueOf(tenantId));
			sysUserService.addUserWithRole(user, selectedRoles);
            sysUserService.addUserWithDepart(user, selectedDeparts);
			result.success("添加成功!");
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			result.error500("操作失败");
		}
		return result;
	}

将前端用户管理新增界面的选择租户删除,新增用户所属租户ID在后端处理,处理方式如上。

修改SysPermissionController 查询用户拥有的菜单权限和按钮权限getUserPermissionByToken方法:

/**
	 * 查询用户拥有的菜单权限和按钮权限
	 *
	 * @return
	 */
	@RequestMapping(value = "/getUserPermissionByToken", method = RequestMethod.GET)
	public Result<?> getUserPermissionByToken(HttpServletRequest req) {
		Result<JSONObject> result = new Result<JSONObject>();
		try {
			//直接获取当前用户不适用前端token
			LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
			if (oConvertUtils.isEmpty(loginUser)) {
				return Result.error("请登录系统!");
			}
			List<SysPermission> metaList = null;


			//检查是否为超级管理员,超级管理员下有多个租户的菜单,只查当前租户菜单
			//判断当前登录用户是否为超级管理员,具体判断方式可根据自身项目情况使用相对应判断方法
	    if(JudgeEnum.YES.getValue().equals(loginUser.getUserFlag())){
				String tenantId = req.getHeader("tenant-id");
				metaList = sysPermissionService.queryPermissionByUser(loginUser.getUsername(),tenantId);
			}else {
				metaList = sysPermissionService.queryByUser(loginUser.getUsername());
			}
			//添加首页路由
			//update-begin-author:taoyan date:20200211 for: TASK #3368 【路由缓存】首页的缓存设置有问题,需要根据后台的路由配置来实现是否缓存
			if(!PermissionDataUtil.hasIndexPage(metaList)){
				SysPermission indexMenu = sysPermissionService.list(new LambdaQueryWrapper<SysPermission>().eq(SysPermission::getName,"首页")).get(0);
				metaList.add(0,indexMenu);
			}
			//update-end-author:taoyan date:20200211 for: TASK #3368 【路由缓存】首页的缓存设置有问题,需要根据后台的路由配置来实现是否缓存
			JSONObject json = new JSONObject();
			JSONArray menujsonArray = new JSONArray();
			this.getPermissionJsonArray(menujsonArray, metaList, null);
			JSONArray authjsonArray = new JSONArray();
			this.getAuthJsonArray(authjsonArray, metaList);
			//查询所有的权限
			LambdaQueryWrapper<SysPermission> query = new LambdaQueryWrapper<SysPermission>();
			query.eq(SysPermission::getDelFlag, CommonConstant.DEL_FLAG_0);
			query.eq(SysPermission::getMenuType, CommonConstant.MENU_TYPE_2);
			//query.eq(SysPermission::getStatus, "1");
			List<SysPermission> allAuthList = sysPermissionService.list(query);
			JSONArray allauthjsonArray = new JSONArray();
			this.getAllAuthJsonArray(allauthjsonArray, allAuthList);
			//路由菜单
			json.put("menu", menujsonArray);
			//按钮权限(用户拥有的权限集合)
			json.put("auth", authjsonArray);
			//全部权限配置集合(按钮权限,访问权限)
			json.put("allAuth", allauthjsonArray);
			result.setResult(json);
			result.success("查询成功");
		} catch (Exception e) {
			result.error500("查询失败:" + e.getMessage());
			log.error(e.getMessage(), e);
		}
		return result;
	}

修改代码:

	//检查是否为超级管理员,超级管理员下有多个租户的菜单,只查当前租户菜单
	//判断当前登录用户是否为超级管理员,具体判断方式可根据自身项目情况使用相对应判断方法
	 if(JudgeEnum.YES.getValue().equals(loginUser.getUserFlag())){
				String tenantId = req.getHeader("tenant-id");
				metaList = sysPermissionService.queryPermissionByUser(loginUser.getUsername(),tenantId);
			}else {
				metaList = sysPermissionService.queryByUser(loginUser.getUsername());
			}

接口类ISysPermissionService 新增:

	/**
	 * 查询租户菜单,不走租户ID附件条件,针对超级管理员admin
	 * @param username
	 * @return
	 */
	public List<SysPermission> queryPermissionByUser(String username,String tenantId);

实现类 SysPermissionServiceImpl 新增:

	/**
	 * 查询租户菜单,不走租户ID附件条件,针对超级管理员admin
	 * @param username
	 * @return
	 */
	@Override
	public List<SysPermission> queryPermissionByUser(String username,String tenantId) {
		return this.sysPermissionMapper.queryPermissionByUser(username,tenantId);
	}

修改 SysPermissionMapper 新增代码:

	/**
	 * 查询租户菜单,不走租户ID附件条件,针对超级管理员admin
	 * @param username
	 * @return
	 */
	@SqlParser(filter = true)
	public List<SysPermission> queryPermissionByUser(@Param("username") String username,@Param("tenantId") String tenantId);

修改 SysPermissionMapper.xml 新增代码:

<!--查询租户菜单,不走租户ID附件条件,针对超级管理员admin-->
	<select id="queryPermissionByUser" parameterType="Object"  resultMap="SysPermission">
		SELECT * FROM (
		SELECT p.*
		FROM  sys_permission p
		WHERE (exists(
		select a.id from sys_role_permission a
		join sys_role b on a.role_id = b.id
		join sys_user_role c on c.role_id = b.id
		join sys_user d on d.id = c.user_id
		where p.id = a.permission_id AND d.username = #{username} and p.tenant_id=#{tenantId}
		)
		or (p.url like '%:code' and p.url like '/online%' and p.hidden = 1)
		or p.url = '/online')
		and p.del_flag = 0
		<!--update begin Author:lvdandan  Date:20200213 for:加入部门权限 -->
		UNION
		SELECT p.*
		FROM  sys_permission p
		WHERE exists(
		select a.id from sys_depart_role_permission a
		join sys_depart_role b on a.role_id = b.id
		join sys_depart_role_user c on c.drole_id = b.id
		join sys_user d on d.id = c.user_id
		where p.id = a.permission_id AND d.username = #{username} and p.tenant_id=#{tenantId}
		)
		and p.del_flag = 0
		<!--update end Author:lvdandan  Date:20200213 for:加入部门权限 -->
		) h order by h.sort_no ASC
	</select>

修改 ShiroRealm中checkUserTokenIsEffect方法:
多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第4张图片
注:此处修改目的,解决超级管理员用户admin登录报错登录已过期
但其他租户用户登录均能正常登录
多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第5张图片
多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第6张图片
接口类 CommonAPI 新增:

 /**
     * 用户登录查询,不进行SQL解析,不添加租户的附加条件
     * @param username
     * @return
     */
    LoginUser getOneUser(String username);

实现类 SysBaseApiImpl 新增:


	@Override
	public LoginUser getOneUser(String username) {
		SysUser user = userMapper.getOneUser(username);

		LoginUser loginUser = new LoginUser();
		BeanUtil.copyProperties(user,loginUser);
		return loginUser;
	}

修改 SysUserMapper 新增代码:


	/**
	 * 用户登录查询,不进行SQL解析,不添加租户的附加条件
	 * @param userName
	 * @return
	 */
	@SqlParser(filter = true)
	SysUser getOneUser(String userName);

修改 SysUserMapper.xml 新增代码:

<!--用户登录查询,不进行SQL解析,不添加租户的附加条件-->
	<select id="getOneUser" parameterType="string" resultType="org.jeecg.modules.system.entity.SysUser">
		select * from  sys_user  where username = #{username} and del_flag = 0
	</select>

****配置过滤过滤器,若不用后面的TenantHandlerInterceptor.java 此处可省略 ** **

import org.jeecg.filter.MyHttpServletRequestFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @Author jianghx
 * @Date 2022/3/8 12:45
 * @Version 1.0
 **/
@Component
public class FilterConfiguration {

    /**
     * 注册过滤器bean MyHttpServletRequestFilter
     *  针对于登录接口  /sys/login、/sys/mLogin
     *  登录接口在拦截器(TenantHandlerInterceptor)中会取出request中的body参数
     * @return
     */
    @Bean
    public FilterRegistrationBean MyHttpServletRequestFilter() {

        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new MyHttpServletRequestFilter());
        //登录接口 url ,具体url 根据项目进行相应修改
        registration.addUrlPatterns("/sys/login","/sys/mLogin");

        registration.setName("MyHttpServletRequestFilter");
        registration.setOrder(1000);
        return registration;
    }

}

MyPathMatchingFilterChainResolver.java



import org.apache.shiro.util.PatternMatcher;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.springframework.stereotype.Component;

import javax.servlet.ServletRequest;

/**
 * @Author jianghx
 * @Date 2022/3/3 15:15
 * @Version 1.0
 **/
@Component
public class MyPathMatchingFilterChainResolver extends PathMatchingFilterChainResolver {


    public String  getMyPathWithinApplication(ServletRequest request){
        return super.getPathWithinApplication(request);
    }

    public boolean MyPathMatches2(String pattern, String path) {
        return super.pathMatches(pattern,path);
    }
}

MyHttpServletRequestFilter.java 解决request获取body参数时只能获取一次

import org.jeecg.util.HttpContextUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

/**
 *
 * 解决request获取body参数时只能获取一次,
 * 此处只针对登录接口,原因 登录接口在拦截器(TenantHandlerInterceptor)中会取出request中的body参数
 */
public class MyHttpServletRequestFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if(servletRequest instanceof HttpServletRequest) {
            requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
        }
        //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中
        // 在chain.doFiler方法中传递新的request对象
        if(null == requestWrapper) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            filterChain.doFilter(requestWrapper, servletResponse);
        }
    }

    @Override
    public void destroy() {

    }

    /***
     * HttpServletRequest 包装器
     * 解决: request.getInputStream()只能读取一次的问题
     * 目标: 流可重复读
     */
    public class RequestWrapper extends HttpServletRequestWrapper {

        /**
         * 请求体
         */
        private String mBody;

        public RequestWrapper(HttpServletRequest request) {
            super(request);
            // 将body数据存储起来
            mBody = getBody(request);
        }

        /**
         * 获取请求体
         * @param request 请求
         * @return 请求体
         */
        private String getBody(HttpServletRequest request) {
            return HttpContextUtils.getBodyString(request);
        }

        /**
         * 获取请求体
         * @return 请求体
         */
        public String getBody() {
            return mBody;
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            // 创建字节数组输入流
            final ByteArrayInputStream bais = new ByteArrayInputStream(mBody.getBytes(StandardCharsets.UTF_8));

            return new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener readListener) {

                }

                @Override
                public int read() throws IOException {
                    return bais.read();
                }
            };
        }
    }
}


添加租户登录拦截器TenantHandlerInterceptor.java,在用户登录时对租户状态(是否冻结)、租户是否到期进行检查,可根据自身情况决定是否添加



import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.modules.scmapprovalflow.enums.JudgeEnum;
import org.jeecg.modules.system.entity.SysTenant;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.model.SysLoginModel;
import org.jeecg.modules.system.service.ISysTenantService;
import org.jeecg.modules.system.service.ISysUserService;
import org.jeecg.util.HttpContextUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

/**
 *  针对登录接口进行租户登录检查
 *
 * @Author jianghx
 * @Date 2022/3/3 11:26
 * @Version 1.0
 **/
@Component
@Slf4j
@Configuration
public class TenantHandlerInterceptor extends HandlerInterceptorAdapter {

    //yml文件配置登录接口 /sys/login,/sys/mLogin  
    @Value("${jeecg.shiro.loginUrlPatterns}")
    private String loginUrlPatterns;

    @Autowired
    private ISysTenantService sysTenantService;

    @Autowired
    private ISysUserService sysUserService;

    @Autowired
    private MyPathMatchingFilterChainResolver myPathMatchingFilterChainResolver;


    /**
     * 针对登录接口进行租户状态、租户时间检查
     * tenantCheckInterceptUrls: /sys/login,/sys/mLogin
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        String[] urls = loginUrlPatterns.split(",");
        if(urls.length>0  ){
        String tenantId = request.getHeader(CommonConstant.TENANT_ID);
        //获取当前请求url
        final String requestURI = myPathMatchingFilterChainResolver.getMyPathWithinApplication(request);
        //检查当前请求是否在shirod filterChainDefinitionMap 过滤路径内
        //此处url判断是从滤器源码中复制过来的,与过滤器判断方法保持一致,
        for (String url : urls) {
            if (myPathMatchingFilterChainResolver.MyPathMatches2(url, requestURI) && StringUtils.isNotBlank(tenantId)) {
                String bodyString = HttpContextUtils.getBodyString(request);

                if (StringUtils.isNotBlank(loginUrlPatterns) && StringUtils.isNotBlank(bodyString)) {
                        JSONObject jsonObject = JSONObject.parseObject(bodyString);
                        String userName = jsonObject.getString("username");
                        //查询用户
                        SysUser userInfo = sysUserService.getOneUser(userName);

                        //超级用户登录,不进行过期验证
                        if(ObjectUtil.isNotNull(userInfo)
                                && JudgeEnum.YES.getValue().equals(userInfo.getUserFlag())){
                            return true;
                        }
                        if(ObjectUtil.isNull(userInfo)){
                            throw new NullPointerException("未查询到用户信息!");
                        }



                        SysTenant tenantInfo = sysTenantService.getById(userInfo.getTenantId());
                        // 租户状态状态 1正常 0冻结
                        if(tenantInfo.getStatus() == JudgeEnum.NO.getValue()){
                            throw new RuntimeException("租户已冻结,请联系管理员!");
                        }
                        if (null == tenantInfo.getBeginDate() || null == tenantInfo.getEndDate()) {
                            log.error("租户信息", tenantInfo);
                            log.info("===============tenantId=========" + tenantId);
                            log.error("当前租户开始时间、结束时间不能有null存在!");
                            throw new NullPointerException("参数缺失,请联系管理员");
                        }

                        //检查租户到期时间
                        if (null != tenantInfo.getEndDate()) {
                            Date tenantEndDate = tenantInfo.getEndDate();//租户到期时间

                            int val = DateUtil.compare(tenantEndDate, new Date());
                            if (val < 0) {
                                throw new RuntimeException("租户时间已到期!");
                            }
                        }


                    }
                }
            }
        }
        return true;
    }


}

前端部分
TenantForm.vue 文件,新增租户页面添加角色前缀编码字段
多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第7张图片
UserModal.vue调整
多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第8张图片

多租户使用说明:
前提条件:
参考本文及官方介绍,完成用户、部门、角色、菜单多租户代码构建。

  1. 使用admin账号,在租户管理添加新租户(添加页面的角色编码前缀 用于新租户admin角色前缀)

  2. 新增租户时,会默认创建新租户菜单、角色的关联关系,且会创建新租户的admin角色(角色前缀_admin ,如:角色前缀 tenant1,新租户admin角色 = tenant1_admin
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第9张图片

  3. 退出admin,重新登录,选择新增租户登录进去
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第10张图片

  4. 登录成功之后,创建部门,并为新租户的admin用户及其角色 分配相关权限及菜单(admin 角色在新增租户时已创建)
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第11张图片
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第12张图片
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第13张图片
    在这里插入图片描述
    至此完成用户、角色、菜单、部门的多租户改造

Jeecg-boot 官方系统多租户角色、部门、菜单https://jeecg.blog.csdn.net/article/details/119780074
Jeecg-boot 官方系统多租户角色、部门、菜单

=以下是存在BUG解决============
0. 防止不同租户创建出相同用户账号从而引发出的安全问题

  • 解决方式一:登录界面添加登录登录要素【租户编号】,即登录时需要输入租户编号、账号、密码三个要素

  • 解决方式二:创建用户时,除了超级管理员(admin)以外,其他用户创建用户需要添加格式校验【租户前缀+用户账号,例如:租户前缀 为:zh,用户格式则为:zhLis,可根据自身情况调整】。租户前缀在租户管理新增时有添加且唯一。
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第14张图片
    账号前缀部分会随租户的不同而有所变化

  • 具体实现:
    1). 修改mutation-types.js

多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第15张图片

export const TENANT_INFO = 'Login_TenantInfo'
 import { loginTenant} from "@/api/login"  
	2)修改LoginSelectTenant.vue

在这里插入图片描述

  import { USER_INFO,TENANT_INFO } from "@/store/mutation-types"
  • List item

selectOk方法中加入租户信息查询的方法
多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第16张图片


        loginTenant({tenantId:this.tenant_id}).then((response)=>{
       
          if(response.code =='200'){
            const result = response.result
            const tenantInfo = result.tenantInfo
            Vue.ls.set(TENANT_INFO, tenantInfo, 7 * 24 * 60 * 60 * 1000)
            commit('SET_INFO', tenantInfo)
            resolve();
          }else{
            return
          }
        })
  • login.js
export function loginTenant(data) {
  return axios({
    url: '/sys/loginTenant',
    method: 'get',
    params: data
  })
}
  • LoginController.java

    @ApiOperation("登录接口获取租户信息")
    @RequestMapping(value = "/loginTenant", method = RequestMethod.GET)
    public Result<JSONObject> loginTenant(@RequestParam(name = "tenantId", required = true) Integer tenantId){
        Result<JSONObject> result = new Result<JSONObject>();
        JSONObject obj = new JSONObject();
        //租户信息
        if(ObjectUtil.isNotNull(tenantId) && null != tenantId) {
            SysTenant sysTenant = sysTenantService.getById(tenantId);
            obj.put("tenantInfo", sysTenant);
            result.setResult(obj);
            result.success("成功");
        }else{
            result.error500("参数缺失");
        }

        return result;
    }
  • 修改UserModal.vue
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第17张图片
import { ACCESS_TOKEN,TENANT_INFO } from "@/store/mutation-types"

多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第18张图片

placeholderUserName:null

多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第19张图片

  • 修改用户账户输入框账号格式动态提示
 <a-form-model-item label="用户账号" :labelCol="labelCol" :wrapperCol="wrapperCol" prop="username">
          <a-input :placeholder="placeholderUserName" label-suffix="sss" v-model="model.username" :readOnly="!!model.id"/>
        </a-form-model-item>

  • 修改【新增按钮】方法
    在这里插入图片描述
  add () {
        const tenantInfo = Vue.ls.get(TENANT_INFO);
        this.refresh();
        this.placeholderUserName = '账号格式 ['+tenantInfo.preCode+'XXX],例:'+tenantInfo.preCode+'ZhangSan';
        this.edit({activitiSync:'1',userIdentity:1});
      },
  • 修改提交保存方法(handleSubmit),添加账号格式校验
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第20张图片
   this.model.id = this.userId;
              const tenantInfo = Vue.ls.get(TENANT_INFO);
              let preCode = tenantInfo.preCode;//账号前缀
              if('admin' != this.model.username && (this.model.username.length<=preCode.length 
              ||this.model.username.substring(0,preCode.length) != preCode)){
                that.confirmLoading = false;
                that.$message.warning("账号格式错误!");
                return;
              }
  1. 超级管理(admin)所属租户修改界面角色显示问题 - 问题描述:admin账户是归属租户A(admin在租户B、C也存在角色关系),但在租户A的用户编辑界面会显示其他租户的角色. 多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第21张图片
  • 原因:此处的角色查询方法没有添加租户ID条件
  • 解决办法:修改查询方法(/sys/user/queryUserRole)
    修改SysUserController.java
    多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第22张图片
 @RequestMapping(value = "/queryUserRole", method = RequestMethod.GET)
    public Result<List<String>> queryUserRole(@RequestParam(name = "userid", required = true) String userid) {
        Result<List<String>> result = new Result<>();
        List<String> list = new ArrayList<String>();
//        List userRole = sysUserRoleService.list(new QueryWrapper().lambda().eq(SysUserRole::getUserId, userid));
        List<SysUserRole> userRole = userRoleMapper.listUserRoleByUserId(userid);
        if (userRole == null || userRole.size() <= 0) {
            result.error500("未找到用户相关角色信息");
        } else {
            for (SysUserRole sysUserRole : userRole) {
                list.add(sysUserRole.getRoleId());
            }
            result.setSuccess(true);
            result.setResult(list);
        }
        return result;
    }

修改SysUserRoleMapper.java,添加查询方法

	/**
	 * 查询角色信息
	 * @param userId
	 * @return
	 */
	@Select("SELECT t.id, t.user_id, t.role_id FROM sys_user_role t join sys_role u on u.id = t.role_id WHERE (t.user_id = #{userId} ) ")
	List<SysUserRole> listUserRoleByUserId(@Param("userId") String userId);
  1. 超级管理员(admim) ,用户编辑保存之后(内容上不做任何修改),退出重新登录,没有弹出租户选择的下拉界面
  • 原因:在用户编辑方法中,会重新给sys_user表中的rel_tenant_ids字段重新更新值(更新成当前登录租户ID),导致admin重登录时,只有一个租户。非admin用户rel_tenant_ids字段只有一个值,admin是多个租户id

  • 解决方法,在用户保存方法中,将rel_tenant_ids赋值操作删除。

SysUserController多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第23张图片
3. 通讯录管理查询修改

  • 问题描述:由于查询为多表关联查询,但是原sql没有使用别名,导致多个表的租户ID tenant_id,也没有别名从而查询方法直接报错

修改SysUserMapper.xml
多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第24张图片
多租户改造方案(用户基础模块)角色、部门、菜单可参考官方文档_第25张图片

	<!-- SQL片段:getUserByOrgCode 的 FROM 和 WHERE 部分 -->
	<sql id="getUserByOrgCodeFromSql">
		FROM
		sys_depart d
		INNER JOIN sys_user_depart sud ON sud.dep_id = d.id
		INNER JOIN sys_user u ON u.id = sud.user_id
		WHERE
		u.del_flag = 0 AND d.org_code LIKE '${orgCode}%'

		<if test="userParams != null">
			<if test="userParams.realname != null and userParams.realname != ''">
				AND u.realname LIKE concat(concat('%',#{userParams.realname}),'%')
			</if>
			<if test="userParams.workNo != null and userParams.workNo != ''">
				AND u.work_no LIKE concat(concat('%',#{userParams.workNo}),'%')
			</if>
		</if>
	</sql>

	<!-- 根据 orgCode 查询用户,包括子部门下的用户 -->
	<select id="getUserByOrgCode" resultType="org.jeecg.modules.system.model.SysUserSysDepartModel">
		SELECT
			u.id AS id,
			u.realname AS realname,
			u.avatar AS avatar,
			u.work_no AS workNo,
			u.post AS post,
			u.telephone AS telephone,
			u.email AS email,
			u.phone AS phone,
			d.id AS departId,
			d.depart_name AS departName
		<include refid="getUserByOrgCodeFromSql"/>
		ORDER BY
			d.org_code ASC
	</select>

你可能感兴趣的:(saas,java,spring,sql,数据库,saas)