测试环境配置及带租房的登录功能

This commit is contained in:
2026-07-06 09:28:20 +08:00
parent 7c319a80a4
commit c43915105c
40 changed files with 1975 additions and 468 deletions

View File

@@ -14,7 +14,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.xml.ws.soap.Addressing;
import java.util.List;
/**

View File

@@ -1,170 +0,0 @@
package com.zf.foodpurchase.fdsafe.wxapp.common;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.zf.foodpurchase.common.config.RuoYiConfig;
import com.zf.foodpurchase.common.constant.Constants;
import com.zf.foodpurchase.common.constant.UserConstants;
import com.zf.foodpurchase.common.core.controller.BaseController;
import com.zf.foodpurchase.common.core.domain.AjaxResult;
import com.zf.foodpurchase.common.core.domain.entity.LoginWxUser;
import com.zf.foodpurchase.common.core.domain.entity.User;
import com.zf.foodpurchase.common.core.domain.entity.WxAppUserVO;
import com.zf.foodpurchase.common.core.domain.model.WxAppLoginBody;
import com.zf.foodpurchase.common.core.domain.model.WxAppLoginUser;
import com.zf.foodpurchase.common.exception.ServiceException;
import com.zf.foodpurchase.common.exception.user.UserPasswordNotMatchException;
import com.zf.foodpurchase.common.utils.MessageUtils;
import com.zf.foodpurchase.common.utils.SecurityUtils;
import com.zf.foodpurchase.common.utils.StringUtils;
import com.zf.foodpurchase.common.utils.file.FileUploadUtils;
import com.zf.foodpurchase.common.utils.file.MimeTypeUtils;
import com.zf.foodpurchase.fdsafe.entity.WxAppUser;
import com.zf.foodpurchase.fdsafe.service.IWxAppUserService;
import com.zf.foodpurchase.fdsafe.vo.WxAppUserViewVO;
import com.zf.foodpurchase.framework.manager.AsyncManager;
import com.zf.foodpurchase.framework.manager.factory.AsyncFactory;
import com.zf.foodpurchase.framework.web.service.WxAppLoginService;
import com.zf.foodpurchase.framework.web.service.WxAppTokenService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
import java.util.Date;
/**
* @author:lilj
* @date:2025/5/12
*/
@RestController
@RequestMapping("/wxapp/userLogin")
public class WxAppUserLoginController extends BaseController {
@Autowired
private IWxAppUserService wxAppUserService;
@Autowired
private WxAppTokenService wxAppTokenService;
@Autowired
private WxAppLoginService wxAppLoginService;
private WxAppUserVO loginWxUser;
@PostMapping("/login")
public AjaxResult login(@RequestBody @Valid WxAppLoginBody wxAppLoginBody) {
String token = wxAppLoginService.login(wxAppLoginBody);
AjaxResult ajax = AjaxResult.success();
ajax.put(Constants.TOKEN, token);
return ajax;
}
@GetMapping("/getCurrentUser")
public AjaxResult getCurrentUser() {
WxAppLoginUser loginWxUser = getWxAppLoginUser();
WxAppUserVO user = loginWxUser.getUser();
AjaxResult response = success("查询成功");
response.put("user", user);
return response;
}
/**
* 可修改项:用户密码,昵称,头像,性别
*
* @param user
* @return
*/
@PostMapping("/updateUser")
public AjaxResult updateUser(@RequestBody WxAppUserViewVO user) {
if (user == null || user.getId() == null) {
return error("用户id不能为空");
}
LambdaUpdateWrapper<WxAppUser> updateWrapper = new LambdaUpdateWrapper<WxAppUser>();
updateWrapper.eq(WxAppUser::getId, user.getId());
updateWrapper.set(WxAppUser::getNickname, user.getNickname());
updateWrapper.set(WxAppUser::getRealName, user.getRealName());
updateWrapper.set(WxAppUser::getAvatarUrl, user.getAvatarUrl());
updateWrapper.set(WxAppUser::getGender, user.getGender());
updateWrapper.set(WxAppUser::getUpdateTime, new Date());
boolean isUpdate = wxAppUserService.update(updateWrapper);
if (!isUpdate) {
return error("修改失败");
}
WxAppUserVO newUser = wxAppUserService.getUserVOById(user.getId());
WxAppLoginUser loginUser = getWxAppLoginUser();
// 设置部门名称
WxAppUserVO oldUser = loginUser.getUser();
newUser.setDeptName(oldUser.getDeptName());
loginUser.setUser(newUser);
wxAppTokenService.setLoginWxAppUser(loginUser);
return isUpdate ? success("修改成功") : error("修改失败");
}
/**
* 修改用户密码
*/
@PostMapping("/updateUserPassword")
public AjaxResult updateUserPassword(@RequestBody WxAppUserViewVO user) {
if (user == null || user.getId() == null) {
return error("用户id不能为空");
}
pwdCheck(user.getNewPassword());
WxAppUser old = wxAppUserService.getById(user.getId());
if (old == null) {
return error("用户不存在");
}
// 校验密码是否正确
boolean isMatch = SecurityUtils.matchesPassword(user.getPassword(), old.getPassword());
if (!isMatch) {
throw new ServiceException("原密码有误");
}
WxAppUser updateUser = new WxAppUser();
updateUser.setId(user.getId());
updateUser.setPassword(SecurityUtils.encryptPassword(user.getNewPassword()));
boolean isUpdate = wxAppUserService.updateById(updateUser);
return isUpdate ? success("密码修改成功") : error("密码修改失败");
}
@GetMapping("/logout")
public AjaxResult logout() {
WxAppLoginUser wxAppLoginUser = getWxAppLoginUser();
if (wxAppLoginUser != null) {
String token = wxAppLoginUser.getToken();
wxAppTokenService.delLoginWxAppUser(token);
}
return AjaxResult.success("退出登录成功");
}
public void pwdCheck(String userPwd) {
if (StringUtils.isBlank(userPwd)) {
throw new ServiceException("新密码不能为空");
}
// 密码如果不在指定范围内 错误
if (userPwd.length() < UserConstants.PASSWORD_MIN_LENGTH
|| userPwd.length() > UserConstants.PASSWORD_MAX_LENGTH) {
throw new ServiceException("新密码位数应大于" + UserConstants.PASSWORD_MIN_LENGTH + ",小于" + UserConstants.PASSWORD_MAX_LENGTH + "位");
}
}
/**
* 微信头像上传
*/
@PostMapping(value = "/avatar", produces = "application/json;charset=UTF-8")
public AjaxResult avatar(MultipartFile file) throws Exception {
if (!file.isEmpty()) {
WxAppLoginUser loginWxUser = getWxAppLoginUser();
// 上传文件路径
String filePath = RuoYiConfig.getUploadPath();
// D:/ruoyi/wxprofile/avatar
String avatar = FileUploadUtils.upload(filePath, file, MimeTypeUtils.IMAGE_EXTENSION);
avatar = RuoYiConfig.getImagePrefix() + avatar;
return AjaxResult.success().put("avatarUrl", avatar);
}
return error("上传图片异常");
}
}

View File

@@ -63,12 +63,7 @@ public class SysLoginController {
*/
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody) {
AjaxResult ajax = AjaxResult.success();
// 生成令牌
String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
loginBody.getUuid());
ajax.put(Constants.TOKEN, token);
return ajax;
return loginService.login(loginBody.getTenantId(), loginBody.getUsername(), loginBody.getPassword());
}
/**

View File

@@ -9,16 +9,16 @@ spring:
# url: jdbc:mysql://localhost:3306/zf_shopping_mall?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# username: root
# password: root123456
url: jdbc:mysql://125.46.39.9:3306/zf_school_food?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://39.165.209.170:3307/zf_school_food?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: wXn*QFs_6aNvFq@qDr.gPYsfBL-XN_o2
password: zheng_wXn_fan*QFs_6aNvFq@keji_qDr.gPYsfBL
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
#enabled: false
url: jdbc:mysql://39.165.209.170:3307/zf_shopping_mall?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: zheng_wXn_fan*QFs_6aNvFq@keji_qDr.gPYsfBL
# 初始连接数
initialSize: 5
# 最小连接池数量

View File

@@ -0,0 +1,35 @@
package com.zf.foodpurchase.common.constant;
/**
* 租户常量信息
*
* @author Lion Li
*/
public interface TenantConstants {
/**
* 超级管理员ID
*/
Long SUPER_ADMIN_ID = 1L;
/**
* 超级管理员角色 roleKey
*/
String SUPER_ADMIN_ROLE_KEY = "super_admin";
/**
* 租户管理员角色 roleKey
*/
String TENANT_ADMIN_ROLE_KEY = "tenant_admin";
/**
* 租户管理员角色名称
*/
String TENANT_ADMIN_ROLE_NAME = "租户管理员";
/**
* 默认租户ID
*/
String DEFAULT_TENANT_ID = "000000";
}

View File

@@ -25,6 +25,10 @@ public class SysDept extends BaseEntity
/** 父商户ID */
private Long parentId;
private String tenantId;
private String isTenant;
/** 祖级列表 */
private String ancestors;
@@ -235,11 +239,29 @@ public class SysDept extends BaseEntity
this.children = children;
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
public String getIsTenant() {
return isTenant;
}
public void setIsTenant(String isTenant) {
this.isTenant = isTenant;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("deptId", getDeptId())
.append("parentId", getParentId())
.append("tenantId", getTenantId())
.append("isTenant", getIsTenant())
.append("ancestors", getAncestors())
.append("deptName", getDeptName())
.append("orderNum", getOrderNum())

View File

@@ -35,6 +35,8 @@ public class SysRole extends BaseEntity
@Excel(name = "角色排序")
private Integer roleSort;
private String tenantId;
/** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */
@Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限")
private String dataScope;
@@ -219,6 +221,14 @@ public class SysRole extends BaseEntity
this.permissions = permissions;
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -226,6 +236,7 @@ public class SysRole extends BaseEntity
.append("roleName", getRoleName())
.append("roleKey", getRoleKey())
.append("roleSort", getRoleSort())
.append("tenantId", getTenantId())
.append("dataScope", getDataScope())
.append("menuCheckStrictly", isMenuCheckStrictly())
.append("deptCheckStrictly", isDeptCheckStrictly())

View File

@@ -0,0 +1,112 @@
package com.zf.foodpurchase.common.core.domain.entity;
import com.zf.foodpurchase.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* 租户业务对象 sys_tenant
*
* @author Michelle.Chung
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SysTenantBo extends BaseEntity {
/**
* id
*/
@NotNull(message = "id不能为空")
private Long id;
/**
* 租户编号
*/
private String tenantId;
/**
* 联系人
*/
@NotBlank(message = "联系人不能为空")
private String contactUserName;
/**
* 联系电话
*/
@NotBlank(message = "联系电话不能为空")
private String contactPhone;
/**
* 企业名称
*/
@NotBlank(message = "企业名称不能为空")
private String companyName;
/**
* 用户名(创建系统用户)
*/
@NotBlank(message = "用户名不能为空")
private String username;
/**
* 密码(创建系统用户)
*/
@NotBlank(message = "密码不能为空")
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}", groups = { AddGroup.class })
private String password;
/**
* 统一社会信用代码
*/
private String licenseNumber;
/**
* 地址
*/
private String address;
/**
* 域名
*/
private String domain;
/**
* 企业简介
*/
private String intro;
/**
* 备注
*/
private String remark;
/**
* 租户套餐编号
*/
@NotNull(message = "租户套餐不能为空")
private Long packageId;
/**
* 过期时间
*/
private Date expireTime;
/**
* 用户数量(-1不限制)
*/
private Long accountCount;
/**
* 租户状态(0正常 1停用)
*/
private String status;
private Long deptId;
}

View File

@@ -0,0 +1,49 @@
package com.zf.foodpurchase.common.core.domain.entity;
import com.zf.foodpurchase.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 租户套餐业务对象 sys_tenant_package
*
* @author Michelle.Chung
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SysTenantPackageBo extends BaseEntity {
/**
* 租户套餐id
*/
private Long packageId;
/**
* 套餐名称
*/
private String packageName;
/**
* 关联菜单id
*/
private Long[] menuIds;
/**
* 备注
*/
private String remark;
/**
* 菜单树选择项是否关联显示
*/
private Boolean menuCheckStrictly;
/**
* 状态(0正常 1停用)
*/
private String status;
}

View File

@@ -0,0 +1,93 @@
package com.zf.foodpurchase.common.core.domain.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 租户视图对象 sys_tenant
*
* @author Michelle.Chung
*/
@Data
public class SysTenantVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
private Long id;
/**
* 租户编号
*/
private String tenantId;
/**
* 联系人
*/
private String contactUserName;
/**
* 联系电话
*/
private String contactPhone;
/**
* 企业名称
*/
private String companyName;
/**
* 统一社会信用代码
*/
private String licenseNumber;
/**
* 地址
*/
private String address;
/**
* 域名
*/
private String domain;
/**
* 企业简介
*/
private String intro;
/**
* 备注
*/
private String remark;
/**
* 租户套餐编号
*/
private Long packageId;
/**
* 过期时间
*/
private Date expireTime;
/**
* 用户数量(-1不限制)
*/
private Long accountCount;
/**
* 租户状态(0正常 1停用)
*/
private String status;
private Long deptId;
private Long createDept;
}

View File

@@ -29,6 +29,16 @@ public class SysUser extends BaseEntity {
@Excel(name = "用户序号", type = Type.EXPORT, cellType = ColumnType.NUMERIC, prompt = "用户编号")
private Long userId;
/**
* 学校id
*/
private Integer organiId;
/**
* 租户id
*/
private String tenantId;
/**
* 部门ID
*/
@@ -369,10 +379,28 @@ public class SysUser extends BaseEntity {
this.roleId = roleId;
}
public Integer getOrganiId() {
return organiId;
}
public void setOrganiId(Integer organiId) {
this.organiId = organiId;
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("userId", getUserId())
.append("organiId", getOrganiId())
.append("tenantId", getTenantId())
.append("deptId", getDeptId())
.append("userName", getUserName())
.append("nickName", getNickName())

View File

@@ -7,6 +7,9 @@ package com.zf.foodpurchase.common.core.domain.model;
*/
public class LoginBody
{
private String tenantId;
/**
/**
* 用户名
*/
@@ -66,4 +69,12 @@ public class LoginBody
{
this.uuid = uuid;
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
}

View File

@@ -0,0 +1,44 @@
package com.zf.foodpurchase.common.utils;
import java.io.Serializable;
import java.util.List;
public class PageResult implements Serializable {
private Integer pageIndex;//当前页
private Integer pages;//总页数
private Long totles;//总条数
private List result; //结果集
public Integer getPageIndex() {
return pageIndex;
}
public void setPageIndex(Integer pageIndex) {
this.pageIndex = pageIndex;
}
public Integer getPages() {
return pages;
}
public void setPages(Integer pages) {
this.pages = pages;
}
public Long getTotles() {
return totles;
}
public void setTotles(Long totles) {
this.totles = totles;
}
public List getResult() {
return result;
}
public void setResult(List result) {
this.result = result;
}
}

View File

@@ -1,11 +1,8 @@
package com.zf.foodpurchase.common.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.util.AntPathMatcher;
import com.zf.foodpurchase.common.constant.Constants;
import com.zf.foodpurchase.common.core.text.StrFormatter;
@@ -681,4 +678,20 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
}
return sb.toString();
}
public static List<Long> commaStrToLongList(String str) {
// 1. 处理空值/空字符串:直接返回空集合,避免NPE
if (str == null || str.trim().isEmpty()) {
return new ArrayList<>();
}
// 2. 拆分+清理空格+转换为Long+收集为List
return Arrays.asList(str.split(",")) // 按逗号拆分字符串为数组
.stream() // 转为流处理
.map(String::trim) // 去除每个元素的前后空格(处理" 4 , 5 "的情况)
.filter(s -> !s.isEmpty()) // 过滤拆分后空字符串(比如",,6"拆分出的空元素)
.map(Long::valueOf) // 字符串转Long(非数字会抛NumberFormatException)
.collect(Collectors.toList()); // 收集为List<Long>
}
}

View File

@@ -0,0 +1,131 @@
package com.zf.foodpurchase.framework.config;
import cn.hutool.core.lang.Assert;
import com.zf.foodpurchase.framework.service.ClientUserDetailsService;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
public class ClientLoginAuthProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
/**
* The password used to perform {@link PasswordEncoder#matches(CharSequence, String)}
* on when the user is not found to avoid SEC-2056. This is necessary, because some
* {@link PasswordEncoder} implementations will short circuit if the password is not
* in a valid format.
*/
private volatile String userNotFoundEncodedPassword;
private ClientUserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
public ClientLoginAuthProvider(ClientUserDetailsService clientUserDetailsService) {
this.userDetailsService = clientUserDetailsService;
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
@Override
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
@Override
protected void doAfterPropertiesSet() {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
CustomUsernamePasswordAuthenticationToken auth = (CustomUsernamePasswordAuthenticationToken) authentication;
//多个参数
UserDetails loadedUser = userDetailsService.loadUserByUsername(username,auth.getTenantId());
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
@Override
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
}
}
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
/**
* Sets the PasswordEncoder instance to be used to encode and validate passwords. If
* not set, the password will be compared using
* {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
* @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
* types.
*/
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
protected PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public void setUserDetailsService(ClientUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
protected ClientUserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
this.userDetailsPasswordService = userDetailsPasswordService;
}
}

View File

@@ -0,0 +1,17 @@
package com.zf.foodpurchase.framework.config;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
public class CustomUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
private final String tenantId;
public CustomUsernamePasswordAuthenticationToken(Object principal, Object credentials, String tenantId) {
super(principal, credentials);
this.tenantId = tenantId;
}
public String getTenantId() {
return tenantId;
}
}

View File

@@ -1,5 +1,6 @@
package com.zf.foodpurchase.framework.config;
import com.zf.foodpurchase.framework.service.ClientUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
@@ -9,8 +10,12 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@@ -23,28 +28,15 @@ import com.zf.foodpurchase.framework.security.filter.JwtAuthenticationTokenFilte
import com.zf.foodpurchase.framework.security.handle.AuthenticationEntryPointImpl;
import com.zf.foodpurchase.framework.security.handle.LogoutSuccessHandlerImpl;
import javax.annotation.Resource;
/**
* spring security配置
*
* @author ruoyi
*/
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig
{
/**
* 自定义用户认证逻辑
*/
@Autowired
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
/**
* 自定义微信小程序/APP认证逻辑类
*/
@Autowired
@Qualifier("wxAppUserDetailsService")
private UserDetailsService wxAppUserDetailsService;
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 认证失败处理类
@@ -75,31 +67,22 @@ public class SecurityConfig
*/
@Autowired
private PermitAllUrlProperties permitAllUrl;
/**
* 身份验证实现
*/
@Bean("authenticationManager")
@Primary
public AuthenticationManager authenticationManager()
{
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
AuthenticationManager manager=new ProviderManager(daoAuthenticationProvider);
return manager;
@Resource
private ClientUserDetailsService clientUserDetailsService;
@Bean
public ClientLoginAuthProvider clientLoginAuthProvider(){
ClientLoginAuthProvider provider = new ClientLoginAuthProvider(clientUserDetailsService);
provider.setPasswordEncoder(bCryptPasswordEncoder());
return provider;
}
/**
* wxApp登录身份验证实现
*/
@Bean("wxAppAuthenticationManager")
public AuthenticationManager wxAppAuthenticationManager()
{
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(wxAppUserDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
AuthenticationManager manager=new ProviderManager(daoAuthenticationProvider);
return manager;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
@@ -117,49 +100,45 @@ public class SecurityConfig
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
{
return httpSecurity
// CSRF禁用,因为不使用session
.csrf(csrf -> csrf.disable())
// 禁用HTTP响应标头
.headers((headersCustomizer) -> {
headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
})
// 认证失败处理类
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
// 基于token,所以不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 注解标记允许匿名访问的url
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
httpSecurity
// CSRF禁用,因为不使用session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage","/wx/user/mockLogin","/wx/user/login","/wxapp/userLogin/login").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 微信静态资源访问不被拦截
.antMatchers("/wx/static/**","/wx/payApi/").permitAll()
// 部分接口开放,不受权限访问限制
.antMatchers("/wx/area/**").permitAll()
// 微信体验功能,部分接口权限放开
// 首页查询商品查询,轮播图查询,类别查询,按类别查询商品接口
.antMatchers("/wx/products/indexProducts","/wx/dict/type/product_category","/wx/products/category","/wx/carousel/list").permitAll()
// 临时放开权限校验,共开发测试使用
// .antMatchers("/wx/**").permitAll()
.antMatchers("/wx/payApi/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
// 添加Logout filter
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)
.build();
.antMatchers("/login", "/register", "/captchaImage","/wx/user/mockLogin","/wx/user/login","/wxapp/userLogin/login").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 微信静态资源访问不被拦截
.antMatchers("/wx/static/**","/wx/payApi/").permitAll()
// 部分接口开放,不受权限访问限制
.antMatchers("/wx/area/**").permitAll()
// 微信体验功能,部分接口权限放开
// 首页查询商品查询,轮播图查询,类别查询,按类别查询商品接口
.antMatchers("/wx/products/indexProducts","/wx/dict/type/product_category","/wx/products/category","/wx/carousel/list").permitAll()
// 临时放开权限校验,共开发测试使用
// .antMatchers("/wx/**").permitAll()
.antMatchers("/wx/payApi/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 添加Logout filter
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
}
/**
@@ -170,4 +149,11 @@ public class SecurityConfig
{
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder());
//auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
auth.authenticationProvider(clientLoginAuthProvider());
}
}

View File

@@ -0,0 +1,9 @@
package com.zf.foodpurchase.framework.service;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public interface ClientUserDetailsService {
UserDetails loadUserByUsername(String username, String tenantId) throws UsernameNotFoundException;
}

View File

@@ -0,0 +1,73 @@
package com.zf.foodpurchase.framework.service.impl;
import com.zf.foodpurchase.common.core.domain.entity.SysUser;
import com.zf.foodpurchase.common.core.domain.model.LoginUser;
import com.zf.foodpurchase.common.enums.UserStatus;
import com.zf.foodpurchase.common.exception.ServiceException;
import com.zf.foodpurchase.common.utils.StringUtils;
import com.zf.foodpurchase.framework.web.service.SysPasswordService;
import com.zf.foodpurchase.framework.web.service.SysPermissionService;
import com.zf.foodpurchase.system.mapper.SysDeptMapper;
import com.zf.foodpurchase.system.mapper.SysRoleDeptMapper;
import com.zf.foodpurchase.system.mapper.SysUserMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.zf.foodpurchase.framework.service.ClientUserDetailsService;
import javax.annotation.Resource;
@Service("clientUserDetailsService")
public class ClientUserDetailsServiceImpl implements ClientUserDetailsService {
private static final Logger log = LoggerFactory.getLogger(ClientUserDetailsServiceImpl.class);
@Resource
private SysUserMapper sysUserMapper;
@Autowired
private SysPasswordService passwordService;
@Autowired
private SysPermissionService permissionService;
@Autowired
private SysRoleDeptMapper roleDeptMapper;
@Autowired
private SysDeptMapper deptMapper;
@Override
public UserDetails loadUserByUsername(String username, String tenantId) throws UsernameNotFoundException {
// 此处即可自定义查询用户
SysUser user = sysUserMapper.selectUserByUserNameAndTenantId(username,tenantId);
if (StringUtils.isNull(user))
{
log.info("登录用户:{} 不存在.", username);
throw new ServiceException("登录用户:" + username + " 不存在");
}
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
log.info("登录用户:{} 已被删除.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
}
else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
log.info("登录用户:{} 已被停用.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}
passwordService.validate(user);
return createLoginUser(user);
}
public UserDetails createLoginUser(SysUser user)
{
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}
}

View File

@@ -1,8 +1,11 @@
package com.zf.foodpurchase.framework.web.service;
import cn.hutool.core.util.ObjectUtil;
import com.zf.foodpurchase.common.constant.CacheConstants;
import com.zf.foodpurchase.common.constant.Constants;
import com.zf.foodpurchase.common.constant.UserConstants;
import com.zf.foodpurchase.common.core.domain.AjaxResult;
import com.zf.foodpurchase.common.core.domain.entity.SysTenantVo;
import com.zf.foodpurchase.common.core.domain.entity.SysUser;
import com.zf.foodpurchase.common.core.domain.model.LoginUser;
import com.zf.foodpurchase.common.core.redis.RedisCache;
@@ -12,10 +15,12 @@ import com.zf.foodpurchase.common.utils.DateUtils;
import com.zf.foodpurchase.common.utils.MessageUtils;
import com.zf.foodpurchase.common.utils.StringUtils;
import com.zf.foodpurchase.common.utils.ip.IpUtils;
import com.zf.foodpurchase.framework.config.CustomUsernamePasswordAuthenticationToken;
import com.zf.foodpurchase.framework.manager.AsyncManager;
import com.zf.foodpurchase.framework.manager.factory.AsyncFactory;
import com.zf.foodpurchase.framework.security.context.AuthenticationContextHolder;
import com.zf.foodpurchase.system.service.ISysConfigService;
import com.zf.foodpurchase.system.service.ISysTenantService;
import com.zf.foodpurchase.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -26,6 +31,9 @@ import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 登录校验方法
@@ -39,7 +47,6 @@ public class SysLoginService
private TokenService tokenService;
@Resource
@Qualifier("authenticationManager")
private AuthenticationManager authenticationManager;
@Autowired
@@ -51,26 +58,39 @@ public class SysLoginService
@Autowired
private ISysConfigService configService;
@Autowired
private ISysTenantService tenantService;
/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public String login(String username, String password, String code, String uuid)
public AjaxResult login(String tenantId, String username, String password)
{
// 验证码校验
validateCaptcha(username, code, uuid);
//validateCaptcha(username, code, uuid);
// 登录前置校验
loginPreCheck(username, password);
//loginPreCheck(username, password);
if (StringUtils.isBlank(tenantId)) {
return AjaxResult.error("请选择租户");
}
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
if (ObjectUtil.isNull(tenant)) {
return AjaxResult.error("租户不存在");
} else if ("1".equals(tenant.getStatus())) {
return AjaxResult.error("租户已停用");
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())
&& new Date().after(tenant.getExpireTime())) {
return AjaxResult.error("租户已过期");
}
// 用户验证
Authentication authentication = null;
try
{
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
CustomUsernamePasswordAuthenticationToken authenticationToken = new CustomUsernamePasswordAuthenticationToken(username, password,tenantId);
AuthenticationContextHolder.setContext(authenticationToken);
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(authenticationToken);
@@ -96,7 +116,12 @@ public class SysLoginService
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUserId());
// 生成token
return tokenService.createToken(loginUser);
String token = tokenService.createToken(loginUser);
Map<String, Object> map = new HashMap<String, Object>();
map.put(Constants.TOKEN, token);
map.put("userId",loginUser.getUserId());
return AjaxResult.success(map);
}
/**

View File

@@ -1,185 +0,0 @@
package com.zf.foodpurchase.framework.web.service;
import com.zf.foodpurchase.common.constant.CacheConstants;
import com.zf.foodpurchase.common.constant.Constants;
import com.zf.foodpurchase.common.constant.UserConstants;
import com.zf.foodpurchase.common.core.domain.entity.SysUser;
import com.zf.foodpurchase.common.core.domain.entity.WxAppUserVO;
import com.zf.foodpurchase.common.core.domain.model.LoginUser;
import com.zf.foodpurchase.common.core.domain.model.WxAppLoginBody;
import com.zf.foodpurchase.common.core.domain.model.WxAppLoginUser;
import com.zf.foodpurchase.common.core.redis.RedisCache;
import com.zf.foodpurchase.common.exception.ServiceException;
import com.zf.foodpurchase.common.exception.user.*;
import com.zf.foodpurchase.common.utils.DateUtils;
import com.zf.foodpurchase.common.utils.MessageUtils;
import com.zf.foodpurchase.common.utils.StringUtils;
import com.zf.foodpurchase.common.utils.ip.IpUtils;
import com.zf.foodpurchase.fdsafe.entity.WxAppUser;
import com.zf.foodpurchase.fdsafe.service.IWxAppUserService;
import com.zf.foodpurchase.framework.manager.AsyncManager;
import com.zf.foodpurchase.framework.manager.factory.AsyncFactory;
import com.zf.foodpurchase.framework.security.context.AuthenticationContextHolder;
import com.zf.foodpurchase.system.service.ISysConfigService;
import com.zf.foodpurchase.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 登录校验方法
*
* @author ruoyi
*/
@Component
public class WxAppLoginService
{
@Autowired
private WxAppTokenService tokenService;
@Autowired
@Qualifier("wxAppAuthenticationManager")
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Autowired
private IWxAppUserService wxAppUserService;
@Autowired
private ISysConfigService configService;
/**
* 登录验证
*
* @param wxAppLoginBody 登录对象
* @return 结果
*/
public String login(WxAppLoginBody wxAppLoginBody)
{
String username = wxAppLoginBody.getUsername();
String password = wxAppLoginBody.getPassword();
// 验证码校验
// validateCaptcha(username, code, uuid);
// 登录前置校验
loginPreCheck(username, password);
// 用户验证
Authentication authentication = null;
try
{
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
AuthenticationContextHolder.setContext(authenticationToken);
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(authenticationToken);
}
catch (Exception e)
{
if (e instanceof BadCredentialsException)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
else
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
throw new ServiceException(e.getMessage());
}
}
finally
{
AuthenticationContextHolder.clearContext();
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
WxAppLoginUser loginUser = (WxAppLoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUserId());
// 生成token
return tokenService.createWxAppToken(loginUser);
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public void validateCaptcha(String username, String code, String uuid)
{
boolean captchaEnabled = configService.selectCaptchaEnabled();
if (captchaEnabled)
{
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
String captcha = redisCache.getCacheObject(verifyKey);
if (captcha == null)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
throw new CaptchaExpireException();
}
redisCache.deleteObject(verifyKey);
if (!code.equalsIgnoreCase(captcha))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
throw new CaptchaException();
}
}
}
/**
* 登录前置校验
* @param username 用户名
* @param password 用户密码
*/
public void loginPreCheck(String username, String password)
{
// 用户名或密码为空 错误
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
throw new UserNotExistsException();
}
// 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// IP黑名单校验
String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
throw new BlackListException();
}
}
/**
* 记录登录信息
*
* @param userId 用户ID
*/
public void recordLoginInfo(Long userId)
{
WxAppUser user = new WxAppUser();
user.setId(userId);
user.setLoginTime(DateUtils.getNowDate());
user.setLoginIp(IpUtils.getIpAddr());
user.setUpdateTime(DateUtils.getNowDate());
wxAppUserService.updateById(user);
}
}

View File

@@ -3,7 +3,6 @@ package com.zf.foodpurchase.fdsafe.vo;
import com.zf.foodpurchase.common.valigroud.UpdateGroup;
import com.zf.foodpurchase.common.vo.CommonVO;
import lombok.Data;
import org.w3c.dom.stylesheets.LinkStyle;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;

View File

@@ -0,0 +1,103 @@
package com.zf.foodpurchase.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zf.foodpurchase.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 租户对象 sys_tenant
*
* @author Michelle.Chung
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_tenant")
public class SysTenant extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId(value = "id")
private Long id;
/**
* 租户编号
*/
private String tenantId;
/**
* 联系人
*/
private String contactUserName;
/**
* 联系电话
*/
private String contactPhone;
/**
* 企业名称
*/
private String companyName;
/**
* 统一社会信用代码
*/
private String licenseNumber;
/**
* 地址
*/
private String address;
/**
* 域名
*/
private String domain;
/**
* 企业简介
*/
private String intro;
/**
* 备注
*/
private String remark;
/**
* 租户套餐编号
*/
private Long packageId;
/**
* 过期时间
*/
private Date expireTime;
/**
* 用户数量(-1不限制)
*/
private Long accountCount;
/**
* 租户状态(0正常 1停用)
*/
private String status;
/**
* 删除标志(0代表存在 1代表删除)
*/
@TableLogic
private String delFlag;
private Long createDept;
}

View File

@@ -0,0 +1,71 @@
package com.zf.foodpurchase.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zf.foodpurchase.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 租户套餐对象 sys_tenant_package
*
* @author Michelle.Chung
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_tenant_package")
public class SysTenantPackage extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 租户套餐id
*/
@TableId(value = "package_id")
private Long packageId;
/**
* 套餐名称
*/
private String packageName;
/**
* 关联菜单id
*/
private String menuIds;
/**
* 备注
*/
private String remark;
/**
* 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示)
*/
private Boolean menuCheckStrictly;
/**
* 状态(0正常 1停用)
*/
private String status;
/**
* 删除标志(0代表存在 1代表删除)
*/
@TableLogic
private String delFlag;
private Long createDept;
private String createBy;
private Date createTime;
private String updateBy;
private Date updateTime;
}

View File

@@ -0,0 +1,50 @@
package com.zf.foodpurchase.system.domain.vo;
import lombok.Data;
import java.io.Serializable;
/**
* 租户套餐视图对象 sys_tenant_package
*
* @author Michelle.Chung
*/
@Data
public class SysTenantPackageVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 租户套餐id
*/
private Long packageId;
/**
* 套餐名称
*/
private String packageName;
/**
* 关联菜单id
*/
private String menuIds;
/**
* 备注
*/
private String remark;
/**
* 菜单树选择项是否关联显示
*/
private Boolean menuCheckStrictly;
/**
* 状态(0正常 1停用)
*/
private String status;
}

View File

@@ -115,4 +115,12 @@ public interface SysDeptMapper
* @return 结果
*/
public int deleteDeptById(Long deptId);
public int updateDeptByTenantId(@Param("dept") SysDept dept, @Param("tenantId") String tenantId);
public List<SysDept> selectByTenantId(@Param("tenantId") String tenantId);
public void updateTenantId(@Param("deptIds") List<Long> deptIds, @Param("tenantId") String tenantId);
public List<Long> selectAllZSByDeptId(Long deptId);
}

View File

@@ -104,4 +104,6 @@ public interface SysRoleMapper
* @return 结果
*/
public int deleteRoleByIds(Long[] roleIds);
public List<SysRole> selectRolesByTenantId(String tenantId);
}

View File

@@ -2,6 +2,7 @@ package com.zf.foodpurchase.system.mapper;
import java.util.List;
import com.zf.foodpurchase.system.domain.SysRoleMenu;
import org.apache.ibatis.annotations.Param;
/**
* 角色与菜单关联表 数据层
@@ -41,4 +42,6 @@ public interface SysRoleMenuMapper
* @return 结果
*/
public int batchRoleMenu(List<SysRoleMenu> roleMenuList);
public int deleteRoleMenuForSync(@Param("roleIds") List<Long> roleIds, @Param("menuIds") List<Long> menuIds);
}

View File

@@ -0,0 +1,35 @@
package com.zf.foodpurchase.system.mapper;
import com.zf.foodpurchase.common.core.domain.entity.SysTenantBo;
import com.zf.foodpurchase.common.core.domain.entity.SysTenantVo;
import com.zf.foodpurchase.system.domain.SysTenant;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface SysTenantMapper {
public SysTenantVo queryById(Long id);
public List<SysTenantVo> queryByCondition(SysTenantBo bo);
public int deleteByIds(@Param("ids") List<Long> ids);
public int updateById(SysTenant sysTenant);
public int insert(SysTenant sysTenant);
public SysTenantVo queryByTenantId(String tenantId);
public List<String> selectObjs();
public List<SysTenantVo> queryByPackageIds(@Param("packageIds") List<Long> packageIds);
public List<SysTenantVo> queryByCompanyName(String companyName);
public List<SysTenantVo> queryByPhone(String phone);
public List<SysTenantVo> queryByTenantIds(@Param("tenantIds") List<String> tenantIds);
}

View File

@@ -0,0 +1,24 @@
package com.zf.foodpurchase.system.mapper;
import com.zf.foodpurchase.common.core.domain.entity.SysTenantPackageBo;
import com.zf.foodpurchase.system.domain.SysTenantPackage;
import com.zf.foodpurchase.system.domain.vo.SysTenantPackageVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface SysTenantPackageMapper {
public SysTenantPackageVo selectById(Long packageId);
public List<SysTenantPackageVo> selectList(SysTenantPackageBo sysTenantPackageBo);
public int insert(SysTenantPackage sysTenantPackage);
public int update(SysTenantPackage sysTenantPackage);
public int deleteByIds(@Param("packageIds") List<Long> packageIds);
}

View File

@@ -124,4 +124,14 @@ public interface SysUserMapper
* @return 结果
*/
public SysUser checkEmailUnique(String email);
int unableUser(@Param("userIds") List<Long> userIds,@Param("statusChar") String statusChar);
List<Long> selectUserIdsByTenantId(@Param("tenantId") String tenantId);
int updateTenantIdOfUsers(@Param("deptIds") List<Long> deptIds,@Param("tenantId") String tenantId);
SysUser selectUserByUserNameAndTenantId(@Param("userName") String userName,@Param("tenantId") String tenantId);
}

View File

@@ -0,0 +1,86 @@
package com.zf.foodpurchase.system.service;
import com.zf.foodpurchase.common.core.domain.AjaxResult;
import com.zf.foodpurchase.common.core.domain.entity.SysTenantBo;
import com.zf.foodpurchase.common.core.domain.entity.SysTenantVo;
import java.util.List;
/**
* 租户Service接口
*
* @author Michelle.Chung
*/
public interface ISysTenantService {
/**
* 查询租户
*/
SysTenantVo queryById(Long id);
/**
* 基于租户ID查询租户
*/
SysTenantVo queryByTenantId(String tenantId);
/**
* 查询租户列表
*/
AjaxResult queryPageList(SysTenantBo bo, Integer page, Integer pageSize);
/**
* 查询租户列表
*/
AjaxResult queryList(SysTenantBo bo);
/**
* 新增租户
*/
Boolean insertByBo(SysTenantBo bo);
/**
* 修改租户
*/
Boolean updateByBo(SysTenantBo bo);
/**
* 校验租户是否允许操作
*/
void checkTenantAllowed(String tenantId);
/**
* 校验并批量删除租户信息
*/
Boolean deleteWithValidByIds(List<Long> ids, Boolean isValid);
/**
* 校验企业名称是否唯一
*/
boolean checkCompanyNameUnique(SysTenantBo bo);
/**
* 校验联系方式是否唯一
*/
boolean checkPhoneUnique(SysTenantBo bo);
/**
* 校验用户名是否唯一
*/
boolean checkUsername(SysTenantBo bo);
/**
* 校验有效期
*/
boolean checkExpireTime(String tenantId);
/**
* 修改租户状态
*/
int updateTenantStatus(SysTenantBo bo);
Boolean deptToTenant(SysTenantBo bo);
}

View File

@@ -0,0 +1,492 @@
package com.zf.foodpurchase.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zf.foodpurchase.common.constant.TenantConstants;
import com.zf.foodpurchase.common.core.domain.AjaxResult;
import com.zf.foodpurchase.common.core.domain.entity.*;
import com.zf.foodpurchase.common.core.domain.model.LoginUser;
import com.zf.foodpurchase.common.exception.ServiceException;
import com.zf.foodpurchase.common.utils.PageResult;
import com.zf.foodpurchase.common.utils.SecurityUtils;
import com.zf.foodpurchase.common.utils.StringUtils;
import com.zf.foodpurchase.common.utils.spring.SpringUtils;
import com.zf.foodpurchase.system.domain.SysRoleDept;
import com.zf.foodpurchase.system.domain.SysRoleMenu;
import com.zf.foodpurchase.system.domain.SysTenant;
import com.zf.foodpurchase.system.domain.SysUserRole;
import com.zf.foodpurchase.system.domain.vo.SysTenantPackageVo;
import com.zf.foodpurchase.system.mapper.*;
import com.zf.foodpurchase.system.service.ISysTenantService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
/**
* 租户Service业务层处理
*
* @author Michelle.Chung
*/
@RequiredArgsConstructor
@Service
public class SysTenantServiceImpl implements ISysTenantService {
private final SysTenantMapper baseMapper;
private final SysTenantPackageMapper tenantPackageMapper;
private final SysUserMapper userMapper;
private final SysDeptMapper deptMapper;
private final SysRoleMapper roleMapper;
private final SysRoleMenuMapper roleMenuMapper;
private final SysRoleDeptMapper roleDeptMapper;
private final SysUserRoleMapper userRoleMapper;
private final SysTenantMapper sysTenantMapper;
private final SysUserMapper sysUserMapper;
private final SysDeptMapper sysDeptMapper;
/**
* 查询租户
*/
@Override
public SysTenantVo queryById(Long id) {
SysTenantVo sysTenantVo = baseMapper.queryById(id);
return sysTenantVo;
}
/**
* 基于租户ID查询租户
*/
@Override
public SysTenantVo queryByTenantId(String tenantId) {
return baseMapper.queryByTenantId(tenantId);
}
/**
* 查询租户列表
*/
@Override
public AjaxResult queryPageList(SysTenantBo bo, Integer page, Integer pageSize) {
PageHelper.startPage(page, pageSize);
List<SysTenantVo> sysTenantVos = baseMapper.queryByCondition(bo);
PageInfo<SysTenantVo> pageInfo = new PageInfo<>(sysTenantVos);
PageResult result = new PageResult();
result.setPageIndex(page);
result.setPages(pageInfo.getPages());
result.setTotles(pageInfo.getTotal());
result.setResult(sysTenantVos);
return AjaxResult.success(result);
}
/**
* 查询租户列表
*/
@Override
public AjaxResult queryList(SysTenantBo bo) {
List<SysTenantVo> sysTenantVos = baseMapper.queryByCondition(bo);
return AjaxResult.success(sysTenantVos);
}
/**
* 新增租户
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(SysTenantBo bo) {
Date date = new Date();
LoginUser loginUser = (LoginUser) SecurityUtils.getLoginUser();
SysTenant add = BeanUtil.toBean(bo, SysTenant.class);
// 获取所有租户编号
List<String> tenantIds = baseMapper.selectObjs();
String tenantId = generateTenantId(tenantIds);
add.setTenantId(tenantId);
add.setCreateDept(bo.getDeptId());
add.setCreateTime(new Date());
add.setCreateBy(loginUser.getUserId()+"");
add.setStatus("0");
boolean flag = baseMapper.insert(add) > 0;
if (!flag) {
throw new ServiceException("创建租户失败");
}
bo.setId(add.getId());
// 根据套餐创建角色
Long roleId = createTenantRole(tenantId, bo.getPackageId(),bo.getCompanyName());
// 创建部门: 公司名是部门名称
SysDept dept = new SysDept();
dept.setTenantId(tenantId);
dept.setDeptName(bo.getCompanyName());
if(!ObjectUtil.isEmpty(bo.getDeptId())){
dept.setParentId(bo.getDeptId());
SysDept parentDept = deptMapper.selectDeptById(bo.getDeptId());
String ancestors = parentDept.getAncestors()+","+bo.getDeptId();
dept.setAncestors(ancestors);
}else{
dept.setParentId(0L);
dept.setAncestors("0");
}
dept.setIsTenant("1");
dept.setCreateTime(date);
dept.setStatus("0");
dept.setDelFlag("0");
deptMapper.insertDept(dept);
Long deptId = dept.getDeptId();
// 角色和部门关联表
SysRoleDept roleDept = new SysRoleDept();
roleDept.setRoleId(roleId);
roleDept.setDeptId(deptId);
roleDeptMapper.batchRoleDept(Arrays.asList(roleDept));
// 创建系统用户
SysUser user = new SysUser();
user.setTenantId(tenantId);
user.setUserName(bo.getUsername());
user.setNickName(bo.getUsername());
user.setPassword(BCrypt.hashpw(bo.getPassword()));
user.setDeptId((long) deptId);
user.setCreateTime(date);
user.setUpdateTime(date);
user.setDelFlag("0");
user.setPhonenumber(bo.getContactPhone());
user.setStatus("0");
userMapper.insertUser(user);
//新增系统用户后,默认当前用户为部门的负责人
SysDept sd = new SysDept();
sd.setLeader(user.getUserName().toString());
sd.setDeptId(deptId);
deptMapper.updateDept(sd);
// 用户和角色关联表
SysUserRole userRole = new SysUserRole();
userRole.setUserId(user.getUserId());
userRole.setRoleId(roleId);
userRoleMapper.batchUserRole(Arrays.asList(userRole));
return true;
}
/**
* 生成租户id
*
* @param tenantIds 已有租户id列表
* @return 租户id
*/
private String generateTenantId(List<String> tenantIds) {
// 随机生成6位
String numbers = RandomUtil.randomNumbers(6);
// 判断是否存在,如果存在则重新生成
if (tenantIds.contains(numbers)) {
return generateTenantId(tenantIds);
}
return numbers;
}
/**
* 根据租户菜单创建租户角色
*
* @param tenantId 租户编号
* @param packageId 租户套餐id
* @return 角色id
*/
private Long createTenantRole(String tenantId, Long packageId, String companyName) {
// 获取租户套餐
SysTenantPackageVo tenantPackageVo = tenantPackageMapper.selectById(packageId);
if (ObjectUtil.isNull(tenantPackageVo)) {
throw new ServiceException("套餐不存在");
}
// 获取套餐菜单id
List<Long> menuIds = StrUtil.split(tenantPackageVo.getMenuIds(), ',')
.stream()
.filter(Objects::nonNull)
.map(Convert::toLong)
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 创建角色
SysRole role = new SysRole();
role.setTenantId(tenantId);
role.setRoleName(companyName+ TenantConstants.TENANT_ADMIN_ROLE_NAME);
role.setRoleKey(TenantConstants.TENANT_ADMIN_ROLE_KEY + "_" + tenantId);
role.setRoleSort(1);
role.setStatus("0");
roleMapper.insertRole(role);
Long roleId = role.getRoleId();
// 创建角色菜单
List<SysRoleMenu> roleMenus = new ArrayList<>(menuIds.size());
menuIds.forEach(menuId -> {
SysRoleMenu roleMenu = new SysRoleMenu();
roleMenu.setRoleId(roleId);
roleMenu.setMenuId(menuId);
roleMenus.add(roleMenu);
});
if(roleMenus.size() > 0) {
roleMenuMapper.batchRoleMenu(roleMenus);
}
return roleId;
}
/**
* 修改租户
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(SysTenantBo bo) {
LoginUser loginUser = (LoginUser) SecurityUtils.getLoginUser();
SysTenant tenant = new SysTenant();
BeanUtil.copyProperties(bo, tenant);
if(!ObjectUtil.isEmpty(bo.getDeptId())){ //非租户部门
SysDept currentDept = new SysDept();
currentDept.setParentId(bo.getDeptId());
SysDept parentDept = deptMapper.selectDeptById(bo.getDeptId());
String ancestors = parentDept.getAncestors()+","+bo.getDeptId();
currentDept.setAncestors(ancestors);
currentDept.setDeptName(bo.getCompanyName());
deptMapper.updateDeptByTenantId(currentDept, tenant.getTenantId());
}else{
List<SysDept> currentDepts = deptMapper.selectByTenantId(tenant.getTenantId());
SysDept tenantDept = currentDepts.stream().filter(f -> StringUtils.equals(f.getLeader(),"admin")).findFirst().orElse(null);
if(!ObjectUtil.isNull(tenantDept)){
tenantDept.setDeptName(bo.getCompanyName());
deptMapper.updateDept(tenantDept);
}
}
SysTenantVo original = sysTenantMapper.queryById(bo.getId());
if(!ObjectUtil.equal(original.getPackageId(), bo.getPackageId())){
syncTenantPackage(bo.getTenantId(),bo.getPackageId());
}
if(!ObjectUtil.equal(original.getCreateDept(), bo.getDeptId())){
tenant.setCreateDept(bo.getDeptId());
}
tenant.setCreateDept(bo.getDeptId());
tenant.setUpdateBy(loginUser.getUserId()+"");
return baseMapper.updateById(tenant) > 0;
}
public Boolean syncTenantPackage(String tenantId, Long packageId) {
SysTenantPackageVo tenantPackageVO = tenantPackageMapper.selectById(packageId);
List<SysRole> roles = roleMapper.selectRolesByTenantId(tenantId);
List<Long> roleIds = new ArrayList<>(roles.size() - 1);
List<Long> menuIds = StringUtils.commaStrToLongList(tenantPackageVO.getMenuIds());
roles.forEach(item -> {
if (item.getRoleKey().contains(TenantConstants.TENANT_ADMIN_ROLE_KEY)) {
List<SysRoleMenu> roleMenus = new ArrayList<>(menuIds.size());
menuIds.forEach(menuId -> {
SysRoleMenu roleMenu = new SysRoleMenu();
roleMenu.setRoleId(item.getRoleId());
roleMenu.setMenuId(menuId);
roleMenus.add(roleMenu);
});
roleMenuMapper.deleteRoleMenuByRoleId(item.getRoleId());
roleMenuMapper.batchRoleMenu(roleMenus);
} else {
roleIds.add(item.getRoleId());
}
});
if (!roleIds.isEmpty()) {
roleMenuMapper.deleteRoleMenuForSync(roleIds, menuIds);
}
return true;
}
/**
* 校验租户是否允许操作
*
* @param tenantId 租户ID
*/
@Override
public void checkTenantAllowed(String tenantId) {
if (ObjectUtil.isNotNull(tenantId) && TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
throw new ServiceException("不允许操作管理租户");
}
}
/**
* 批量删除租户
*/
@Override
public Boolean deleteWithValidByIds(List<Long> ids, Boolean isValid) {
if (isValid) {
// 做一些业务上的校验,判断是否需要校验
if (ids.contains(TenantConstants.SUPER_ADMIN_ID)) {
throw new ServiceException("超管租户不能删除");
}
}
boolean i = baseMapper.deleteByIds(ids)>0;
List<SysDept> sysDepts = new ArrayList<>();
for(Long id:ids){
SysTenantVo sysTenantVo = sysTenantMapper.queryById(id);
if(ObjectUtil.isNotNull(sysTenantVo)){
List<SysDept> depts = sysDeptMapper.selectByTenantId(sysTenantVo.getTenantId());
sysDepts.addAll(depts);
}
}
for(SysDept s:sysDepts){
s.setIsTenant("0");
sysDeptMapper.updateDept(s);
}
return i;
}
/**
* 校验企业名称是否唯一
*/
@Override
public boolean checkCompanyNameUnique(SysTenantBo bo) {
List<SysTenantVo> list = baseMapper.queryByCompanyName(bo.getCompanyName());
if(StringUtils.isEmpty(bo.getTenantId())){ // 新增
if(list.size()>0){
return false;
}
}else{ //修改
SysTenantVo sysTenantVo = list.stream().filter(item->!bo.getTenantId().equals(item.getTenantId())).findFirst().orElse(null);
if(ObjectUtil.isNotEmpty(sysTenantVo)){
return false;
}
}
return true;
}
/**
* 校验联系方式是否唯一
*/
@Override
public boolean checkPhoneUnique(SysTenantBo bo) {
List<SysTenantVo> list = baseMapper.queryByPhone(bo.getContactPhone());
if(StringUtils.isEmpty(bo.getTenantId())){ // 新增
if(list.size()>0){
return false;
}
}else{ //修改
SysTenantVo sysTenantVo = list.stream().filter(item->!bo.getTenantId().equals(item.getTenantId())).findFirst().orElse(null);
if(ObjectUtil.isNotEmpty(sysTenantVo)){
return false;
}
}
return true;
}
@Override
public boolean checkUsername(SysTenantBo bo) {
SysUser info = userMapper.checkUserNameUnique(bo.getUsername());
if(StringUtils.isEmpty(bo.getTenantId())){
if(ObjectUtil.isNotNull(info)){
return false;
}
}else{
if(ObjectUtil.isNotNull(info)){
if(StringUtils.equals(info.getTenantId(),bo.getTenantId())){
return false;
}
}
}
return true;
}
/**
* 校验有效期
*/
@Override
public boolean checkExpireTime(String tenantId) {
SysTenantVo tenant = SpringUtils.getAopProxy(this).queryByTenantId(tenantId);
// 如果未设置过期时间代表不限制
if (ObjectUtil.isNull(tenant.getExpireTime())) {
return true;
}
// 如果当前时间在过期时间之前则通过
return new Date().before(tenant.getExpireTime());
}
@Override
public int updateTenantStatus(SysTenantBo bo) {
SysTenant tenant = new SysTenant();
tenant.setId(bo.getId());
tenant.setStatus(bo.getStatus());
LoginUser loginUser = (LoginUser) SecurityUtils.getLoginUser();
Long userId =loginUser.getUserId();
SysTenantVo sysTenantVo = baseMapper.queryById(bo.getId());
SysDept sysDept = new SysDept();
sysDept.setUpdateBy(userId+"");
sysDept.setStatus(bo.getStatus());
deptMapper.updateDeptByTenantId(sysDept,sysTenantVo.getTenantId());
List<Long> userIds = sysUserMapper.selectUserIdsByTenantId(sysTenantVo.getTenantId());
if(CollectionUtil.isNotEmpty(userIds)){
sysUserMapper.unableUser(userIds,bo.getStatus());
}
return baseMapper.updateById(tenant);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deptToTenant(SysTenantBo bo) {
Long deptId = bo.getDeptId();
Date date = new Date();
LoginUser loginUser = (LoginUser) SecurityUtils.getLoginUser();
SysTenant add = BeanUtil.toBean(bo, SysTenant.class);
// 获取所有租户编号
List<String> tenantIds = baseMapper.selectObjs();
String tenantId = generateTenantId(tenantIds);
add.setTenantId(tenantId);
add.setCreateDept(deptId);
add.setCreateTime(date);
add.setCreateBy(loginUser.getUserId()+"");
add.setStatus("0");
boolean flag = baseMapper.insert(add) > 0;
if (!flag) {
throw new ServiceException("创建租户失败");
}
// 根据套餐创建角色
Long roleId = createTenantRole(tenantId, bo.getPackageId(),bo.getCompanyName());
// 角色和部门关联表
SysRoleDept roleDept = new SysRoleDept();
roleDept.setRoleId(roleId);
roleDept.setDeptId(deptId);
roleDeptMapper.batchRoleDept(Arrays.asList(roleDept));
// 创建系统用户
SysUser user = new SysUser();
user.setTenantId(tenantId);
user.setUserName("admin");
user.setNickName("admin");
user.setPassword(BCrypt.hashpw("123456"));
user.setDeptId((long) deptId);
user.setCreateTime(date);
user.setUpdateTime(date);
user.setDelFlag("0");
user.setStatus("0");
userMapper.insertUser(user);
//新增系统用户后,默认当前用户为部门的负责人
SysDept sd = new SysDept();
sd.setLeader(user.getUserName().toString());
sd.setDeptId(deptId);
sd.setIsTenant("1");
sd.setTenantId(tenantId);
deptMapper.updateDept(sd);
List<Long> deptIds = deptMapper.selectAllZSByDeptId(deptId);
deptIds.add(deptId);
//更新当前升级为租户的部门下属所有子孙节点的租户ID
deptMapper.updateTenantId(deptIds,tenantId);
//更新当前升级为租户的部门下属所有子孙节点的用户租户ID
userMapper.updateTenantIdOfUsers(deptIds,tenantId);
// 用户和角色关联表
SysUserRole userRole = new SysUserRole();
userRole.setUserId(user.getUserId());
userRole.setRoleId(roleId);
userRoleMapper.batchUserRole(Arrays.asList(userRole));
return true;
}
}

View File

@@ -0,0 +1,149 @@
<?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="com.zf.foodpurchase.system.mapper.SysTenantMapper">
<sql id="selectSysTenantVo">
select id,tenant_id tenantId,contact_user_name contactUserName,contact_phone contactPhone,company_name companyName,license_number licenseNumber,
address,intro,domain,remark,status,
package_id packageId,expire_time expireTime,account_count accountCount,del_flag delFlag,create_dept createDept,create_by createBy,
create_time createTime,update_by updateBy,update_time updateTime from sys_tenant
</sql>
<select id="queryById" resultType="SysTenantVo">
select id,st.tenant_id tenantId,contact_user_name contactUserName,contact_phone contactPhone,company_name companyName,license_number licenseNumber,
address,intro,domain,remark,st.status,st.create_dept deptId,
package_id packageId,expire_time expireTime,account_count accountCount,st.del_flag delFlag,create_dept createDept,st.create_by createBy,
st.create_time createTime,st.update_by updateBy,st.update_time updateTime from sys_tenant st
WHERE st.del_flag = '0' and st.id = #{id}
</select>
<select id="queryByCondition" parameterType="SysTenantBo" resultType="SysTenantVo">
<include refid="selectSysTenantVo"/>
where del_flag = '0'
<if test="tenantId != null and tenantId != ''">
AND tenant_id like concat('%', #{tenantId}, '%')
</if>
<if test="contactUserName != null and contactUserName != ''">
AND contact_user_name like concat('%', #{contactUserName}, '%')
</if>
<if test="contactPhone != null and contactPhone != ''">
AND contact_phone like concat('%', #{contactPhone}, '%')
</if>
<if test="companyName != null and companyName != ''">
AND company_name like concat('%', #{companyName}, '%')
</if>
order by create_time desc
</select>
<select id="queryByCompanyName" parameterType="String" resultType="SysTenantVo">
<include refid="selectSysTenantVo"/>
where del_flag = '0' AND company_name = #{companyName}
order by create_time desc
</select>
<select id="queryByPhone" parameterType="String" resultType="SysTenantVo">
<include refid="selectSysTenantVo"/>
where del_flag = '0' AND contact_phone = #{phone}
order by create_time desc
</select>
<select id="queryByTenantId" parameterType="String" resultType="SysTenantVo">
<include refid="selectSysTenantVo"/>
where del_flag = '0' AND tenant_id = #{tenantId}
</select>
<select id="queryByTenantIds" parameterType="String" resultType="SysTenantVo">
<include refid="selectSysTenantVo"/>
where del_flag = '0' AND tenant_id in
<foreach collection="tenantIds" item="tenantId" open="(" separator="," close=")">
#{tenantId}
</foreach>
</select>
<delete id="deleteByIds" parameterType="Long">
update sys_tenant set del_flag = '1' where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<update id="updateById" parameterType="SysTenant">
update sys_tenant
<set>
<if test="companyName != null and companyName != ''">company_name = #{companyName},</if>
<if test="contactUserName != null and contactUserName != ''">contact_user_name = #{contactUserName},</if>
<if test="contactPhone != null and contactPhone != ''">contact_phone = #{contactPhone},</if>
<if test="expireTime != null">expire_time = #{expireTime},</if>
<if test="accountCount != null">account_count = #{accountCount},</if>
<if test="domain != null and domain != ''">domain = #{domain},</if>
<if test="address != null and address != ''">address = #{address},</if>
<if test="licenseNumber != null">license_number = #{licenseNumber},</if>
<if test="intro != null and intro != ''">intro = #{intro},</if>
<if test="remark != null and remark != ''">remark = #{remark},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="createDept != null and createDept != ''">create_dept = #{createDept},</if>
<if test="packageId != null">package_id = #{packageId},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
update_time = sysdate()
</set>
where id = #{id}
</update>
<insert id="insert" parameterType="SysTenant">
INSERT INTO sys_tenant
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="tenantId != null">tenant_id,</if>
<if test="contactUserName != null">contact_user_name,</if>
<if test="contactPhone != null">contact_phone,</if>
<if test="companyName != null">company_name,</if>
<if test="licenseNumber != null">license_number,</if>
<if test="address != null">address,</if>
<if test="intro != null">intro,</if>
<if test="domain != null">domain,</if>
<if test="remark != null">remark,</if>
<if test="packageId != null">package_id,</if>
<if test="expireTime != null">expire_time,</if>
<if test="accountCount != null">account_count,</if>
<if test="status != null">status,</if>
<if test="delFlag != null">del_flag,</if>
<if test="createDept != null">create_dept,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">
<if test="tenantId != null">#{tenantId},</if>
<if test="contactUserName != null">#{contactUserName},</if>
<if test="contactPhone != null">#{contactPhone},</if>
<if test="companyName != null">#{companyName},</if>
<if test="licenseNumber != null">#{licenseNumber},</if>
<if test="address != null">#{address},</if>
<if test="intro != null">#{intro},</if>
<if test="domain != null">#{domain},</if>
<if test="remark != null">#{remark},</if>
<if test="packageId != null">#{packageId},</if>
<if test="expireTime != null">#{expireTime},</if>
<if test="accountCount != null">#{accountCount},</if>
<if test="status != null">#{status},</if>
<if test="delFlag != null">#{delFlag},</if>
<if test="createDept != null">#{createDept},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<select id="selectObjs" resultType="String">
select tenant_id tenantId from sys_tenant where del_flag = '0'
</select>
<select id="queryByPackageIds" resultType="SysTenantVo">
<include refid="selectSysTenantVo"/>
WHERE del_flag = '0' and package_id in
<foreach collection="packageIds" item="packageId" open="(" separator="," close=")">
#{packageId}
</foreach>
</select>
</mapper>

View File

@@ -0,0 +1,79 @@
<?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="com.zf.foodpurchase.system.mapper.SysTenantPackageMapper">
<sql id="selectSysTenantVo">
select package_id packageId,package_name packageName,menu_ids menuIds,remark,
menu_check_strictly menuCheckStrictly,status,del_flag delFlag,create_dept createDept,create_by createBy,create_time createTime,
update_by updateBy,update_time updateTime from sys_tenant_package
</sql>
<select id="selectById" resultType="SysTenantPackageVo">
<include refid="selectSysTenantVo"/>
where package_id=#{packageId} and del_flag = '0' limit 1
</select>
<select id="selectList" parameterType="SysTenantPackageBo" resultType="SysTenantPackageVo">
<include refid="selectSysTenantVo"/>
where del_flag = '0'
<if test="packageName != null">
AND package_name like concat('%',#{packageName},'%')
</if>
</select>
<delete id="deleteByIds" parameterType="Long">
update sys_tenant_package set del_flag = '1' where package_id in
<foreach collection="packageIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<update id="update" parameterType="SysTenantPackage">
update sys_tenant_package
<trim prefix="SET" suffixOverrides=",">
<if test="packageName != null">package_name = #{packageName},</if>
<if test="menuIds != null">menu_ids = #{menuIds},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="menuCheckStrictly != null">menu_check_strictly = #{menuCheckStrictly},</if>
<if test="status != null">status = #{status},</if>
<if test="delFlag != null">del_flag = #{delFlag},</if>
<if test="createDept != null">create_dept = #{createDept},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
WHERE package_id = #{packageId}
</update>
<insert id="insert" parameterType="SysTenantPackage">
INSERT INTO sys_tenant_package
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="packageName != null">package_name,</if>
<if test="menuIds != null">menu_ids,</if>
<if test="remark != null">remark,</if>
<if test="menuCheckStrictly != null">menu_check_strictly,</if>
<if test="status != null">status,</if>
<if test="delFlag != null">del_flag,</if>
<if test="createDept != null">create_dept,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">
<if test="packageName != null">#{packageName},</if>
<if test="menuIds != null">#{menuIds},</if>
<if test="remark != null">#{remark},</if>
<if test="menuCheckStrictly != null">#{menuCheckStrictly},</if>
<if test="status != null">#{status},</if>
<if test="delFlag != null">#{delFlag},</if>
<if test="createDept != null">#{createDept},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
</mapper>

View File

@@ -6,6 +6,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="SysRole" id="SysRoleResult">
<id property="roleId" column="role_id" />
<id property="tenantId" column="tenant_id" />
<result property="roleName" column="role_name" />
<result property="roleKey" column="role_key" />
<result property="roleSort" column="role_sort" />
@@ -148,5 +149,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{roleId}
</foreach>
</delete>
<select id="selectRolesByTenantId" parameterType="String" resultMap="SysRoleResult">
<include refid="selectRoleVo"/>
where r.tenant_id = #{tenantId}
</select>
</mapper>

View File

@@ -30,5 +30,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
(#{item.roleId},#{item.menuId})
</foreach>
</insert>
<delete id="deleteRoleMenuForSync" parameterType="Long">
delete from sys_role_menu where role_id in
<foreach collection="roleIds" item="roleId" open="(" separator="," close=")">
#{roleId}
</foreach>
<if test="menuIds != null and menuIds.size() > 0">
and menu_id not in
<foreach collection="menuIds" item="menuId" open="(" separator="," close=")">
#{menuId}
</foreach>
</if>
</delete>
</mapper>

View File

@@ -6,7 +6,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="SysUser" id="SysUserResult">
<id property="userId" column="user_id" />
<result property="deptId" column="dept_id" />
<result property="organiId" column="organi_id" />
<result property="tenantId" column="tenant_id" />
<result property="deptId" column="dept_id" />
<result property="userName" column="user_name" />
<result property="nickName" column="nick_name" />
<result property="email" column="email" />
@@ -32,6 +34,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap id="deptResult" type="SysDept">
<id property="deptId" column="dept_id" />
<result property="parentId" column="parent_id" />
<result property="tenantId" column="tenant_id" />
<result property="isTenant" column="is_tenant" />
<result property="deptName" column="dept_name" />
<result property="ancestors" column="ancestors" />
<result property="orderNum" column="order_num" />
@@ -41,6 +45,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap id="RoleResult" type="SysRole">
<id property="roleId" column="role_id" />
<result property="tenantId" column="tenant_id" />
<result property="roleName" column="role_name" />
<result property="roleKey" column="role_key" />
<result property="roleSort" column="role_sort" />
@@ -226,4 +231,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach>
</delete>
<update id="unableUser" parameterType="SysUser">
update sys_user set status = #{statusChar} where user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
</update>
<select id="selectUserIdsByTenantId" resultType="Long">
select distinct user_id from sys_user
where tenant_id = #{tenantId}
</select>
<update id="updateTenantIdOfUsers">
<if test="deptIds != null and deptIds.size() > 0">
update sys_user set tenant_id = #{tenantId} where dept_id in
<foreach collection="deptIds" item="deptId" open="(" close=")" separator=",">
#{deptId}
</foreach>
</if>
</update>
<select id="selectUserByUserNameAndTenantId" resultMap="SysUserResult">
<include refid="selectUserVo"/>
where u.del_flag = '0' and (u.status is not null or u.status =0) and u.user_name = #{userName} and u.tenant_id = #{tenantId}
</select>
</mapper>

View File

@@ -7,6 +7,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="SysDept" id="SysDeptResult">
<id property="deptId" column="dept_id" />
<result property="parentId" column="parent_id" />
<result property="tenantId" column="tenant_id" />
<result property="isTenant" column="is_tenant" />
<result property="ancestors" column="ancestors" />
<result property="deptName" column="dept_name" />
<result property="orderNum" column="order_num" />
@@ -177,4 +179,45 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
update sys_dept set del_flag = '2' where dept_id = #{deptId}
</delete>
<update id="updateDeptByTenantId">
update sys_dept
<set>
<if test="dept.parentId != null and dept.parentId != 0">parent_id = #{dept.parentId},</if>
<if test="dept.deptName != null and dept.deptName != ''">dept_name = #{dept.deptName},</if>
<if test="dept.ancestors != null and dept.ancestors != ''">ancestors = #{dept.ancestors},</if>
<if test="dept.orderNum != null">order_num = #{dept.orderNum},</if>
<if test="dept.leader != null">leader = #{dept.leader},</if>
<if test="dept.phone != null">phone = #{dept.phone},</if>
<if test="dept.email != null">email = #{dept.email},</if>
<if test="dept.status != null and dept.status != ''">status = #{dept.status},</if>
<if test="dept.updateBy != null and dept.updateBy != ''">update_by = #{dept.updateBy},</if>
<if test="dept.isTenant != null and dept.isTenant != ''">is_tenant = #{dept.isTenant},</if>
update_time = sysdate()
</set>
where tenant_id = #{tenantId,jdbcType=VARCHAR}
</update>
<select id="selectByTenantId" parameterType="String" resultMap="SysDeptResult">
<include refid="selectDeptVo"/>
where d.del_flag = '0' and d.tenant_id = #{tenantId}
</select>
<select id="selectAllZSByDeptId" parameterType="Long" resultType="long">
select dept_id
from sys_dept
where find_in_set(#{deptId}, ancestors)
</select>
<update id="updateTenantId">
<if test="deptIds != null and deptIds.size() > 0">
update sys_dept
set tenant_id = #{tenantId}
where dept_id in
<foreach collection="deptIds" item="deptId" index="index"
separator="," open="(" close=")">
#{deptId}
</foreach>
</if>
</update>
</mapper>

10
pom.xml
View File

@@ -212,11 +212,19 @@
<artifactId>javase</artifactId>
<version>3.4.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
</dependencies>
<modules>
<module>fp-admin</module>
<module>fp-framework</module>