feat: 架构优化

master
wangrunpu 2 weeks ago
parent f7853fadfd
commit 2f82177cfd

@ -99,7 +99,7 @@ public class AuthController {
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
}
// 校验租户
loginService.checkTenant(loginBody.getTenantId());
loginService.checkTenant(loginBody.getUsername());
// 登录
LoginVo loginVo = IAuthStrategy.login(body, client, grantType);

@ -63,10 +63,11 @@ public class CaptchaController {
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
String templateId = "";
String templateId = "SMS_249510287";
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
System.out.println("SmsBlend = " + smsBlend);
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
if (!smsResponse.isSuccess()) {
log.error("验证码短信发送异常 => {}", smsResponse);

@ -222,28 +222,28 @@ public class SysLoginService {
/**
*
*
* @param tenantId ID
* @param username
*/
public void checkTenant(String tenantId) {
public void checkTenant(String username) {
if (!TenantHelper.isEnable()) {
return;
}
if (StringUtils.isBlank(tenantId)) {
throw new TenantException("tenant.number.not.blank");
if (StringUtils.isBlank(username)) {
throw new TenantException("user.username.not.blank");
}
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
if (TenantConstants.DEFAULT_USERNAME.equals(username) || TenantConstants.DEFAULT_PHONENUMBER.equals(username)) {
return;
}
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
SysTenantVo tenant = tenantService.queryByUsername(username);
if (ObjectUtil.isNull(tenant)) {
log.info("登录租户:{} 不存在.", tenantId);
log.info("登录公司:{} 不存在.", username);
throw new TenantException("tenant.not.exists");
} else if (SystemConstants.DISABLE.equals(tenant.getStatus())) {
log.info("登录租户:{} 已被停用.", tenantId);
log.info("登录公司:{} 已被停用.", username);
throw new TenantException("tenant.blocked");
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())
&& new Date().after(tenant.getExpireTime())) {
log.info("登录租户:{} 已超过有效期.", tenantId);
log.info("登录公司:{} 已超过有效期.", username);
throw new TenantException("tenant.expired");
}
}

@ -51,19 +51,21 @@ public class PasswordAuthStrategy implements IAuthStrategy {
public LoginVo login(String body, SysClientVo client) {
PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String username = loginBody.getUsername();
String password = loginBody.getPassword();
String code = loginBody.getCode();
String uuid = loginBody.getUuid();
// 先查询用户信息获取租户ID
SysUserVo user = loadUserByUsername(username);
String tenantId = user.getTenantId();
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, code, uuid);
}
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByUsername(username);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
return loginService.buildLoginUser(user);
@ -109,7 +111,15 @@ public class PasswordAuthStrategy implements IAuthStrategy {
}
private SysUserVo loadUserByUsername(String username) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
// 不指定租户上下文,允许查询所有租户的用户
SysUserVo user = TenantHelper.ignore(() ->
userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username))
);
if (ObjectUtil.isNull(user)) {
user = TenantHelper.ignore(() ->
userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, username))
);
}
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);

@ -47,11 +47,14 @@ public class SmsAuthStrategy implements IAuthStrategy {
public LoginVo login(String body, SysClientVo client) {
SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String phonenumber = loginBody.getPhonenumber();
String smsCode = loginBody.getSmsCode();
// 先查询用户信息获取租户ID
SysUserVo user = loadUserByUsername(phonenumber);
String tenantId = user.getTenantId();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByPhonenumber(phonenumber);
loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
@ -87,14 +90,22 @@ public class SmsAuthStrategy implements IAuthStrategy {
return code.equals(smsCode);
}
private SysUserVo loadUserByPhonenumber(String phonenumber) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
private SysUserVo loadUserByUsername(String username) {
// 不指定租户上下文,允许查询所有租户的用户
SysUserVo user = TenantHelper.ignore(() ->
userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username))
);
if (ObjectUtil.isNull(user)) {
user = TenantHelper.ignore(() ->
userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, username))
);
}
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (SystemConstants.DISABLE.equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
return user;
}

@ -172,11 +172,13 @@ sms:
# 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: alibaba
# 有些称为accessKey有些称之为apiKey也有称为sdkKey或者appId。
access-key-id: 您的accessKey
access-key-id: LTAI5t8EFke56vn9YPQSgtMP
# 称为accessSecret有些称之为apiSecret
access-key-secret: 您的accessKeySecret
signature: 您的短信签名
sdk-app-id: 您的sdkAppId
access-key-secret: 1QudSASPkiXrLJON6KQr4Vrojt1rO6
# 短信签名 - 在阿里云短信控制台「签名管理」中申请,填写时不要加【】符号
# 例如: 若羽物联 (不要写成 【若羽物联】)
signature: 速通互联验证码
# 注意: 阿里云不需要 sdk-app-id 参数,已删除
config2:
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: tencent

@ -32,4 +32,14 @@ public interface TenantConstants {
*/
String DEFAULT_TENANT_ID = "000000";
/**
*
*/
String DEFAULT_PHONENUMBER = "13888888888";
/**
*
*/
String DEFAULT_USERNAME = "administrator";
}

@ -35,6 +35,11 @@ public class LoginBody implements Serializable {
*/
private String tenantId;
/**
*
*/
private String username;
/**
*
*/

@ -0,0 +1,12 @@
package org.dromara.demo.domain;
import lombok.Data;
import java.net.Socket;
@Data
public class ClientConnection {
private String clientId; // 你的设备唯一ID
private Socket socket; // 保存socket
private long lastActiveTime; // 最近一次心跳/数据时间
private long connectTime; // 建连时间
}

@ -0,0 +1,313 @@
package org.dromara.demo.server;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.demo.domain.ClientConnection;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import jakarta.annotation.PreDestroy;
/**
* TCP4G
* "hellohello" "byebye" JSON
* 使
*/
@Slf4j
@Component
public class IotTcpServer implements CommandLineRunner {
@Value("${tcp.server.port:8888}")
private int tcpPort;
private final ObjectMapper objectMapper = new ObjectMapper();
private final ExecutorService executorService = Executors.newCachedThreadPool();
private final ScheduledExecutorService heartbeatService = Executors.newScheduledThreadPool(2);
private final AtomicBoolean started = new AtomicBoolean(false);
private ServerSocket serverSocket;
// 用于存储客户端连接和起信息
@Getter
private final ConcurrentHashMap<String, ClientConnection> clientMaps = new ConcurrentHashMap<>();
@Override
public void run(String... args) throws Exception {
executorService.submit(this::startTcpServer);
// 启动心跳检测任务每10秒检查一次
heartbeatService.scheduleAtFixedRate(this::checkHeartbeats, 10, 30, TimeUnit.SECONDS);
}
/**
* TCP
*/
public void startTcpServer() {
if (!started.compareAndSet(false, true)) {
log.warn("TCP服务器已启动忽略重复启动");
return;
}
try {
serverSocket = new ServerSocket(tcpPort);
log.info("TCP服务器启动成功监听端口: {}", tcpPort);
// 持续监听客户端连接
while (!serverSocket.isClosed()) {
try {
Socket clientSocket = serverSocket.accept();
configureClientSocket(clientSocket);
// 为每个客户端连接创建独立的处理线程
executorService.submit(() -> handleClient(clientSocket));
} catch (IOException e) {
if (!serverSocket.isClosed()) {
log.error("接受客户端连接时发生错误", e);
}
}
}
} catch (IOException e) {
log.error("启动TCP服务器失败", e);
}
}
/**
*
* "hellohello" "byebye" JSON
* /
*/
private void handleClient(Socket clientSocket) {
//TODO 应该处理为 首个hellohello 和最后一个bygbye
//并且 如果开头和结尾不为这样的数据,则丢弃
final String START = "hellohello";
final String END = "byebye";
final String HEARTBEAT = "heartbeat";
final Charset charset = StandardCharsets.UTF_8;
String clientId = null;
try (InputStream in = clientSocket.getInputStream()) {
StringBuilder sb = new StringBuilder(4096);
byte[] buf = new byte[4096];
int n;
// 第一个完整的帧包含设备唯一标识符
boolean identifierReceived = false;
while ((n = in.read(buf)) != -1) {
if (n <= 0) continue;
String chunk = new String(buf, 0, n, charset);
sb.append(chunk);
while (true) {
int s = sb.indexOf(START);
if (s < 0) {
// 可选:避免缓冲区无限增长(很少触达)
if (sb.length() > 65536) {
sb.delete(0, sb.length() - 65536);
}
break;
}
int e = sb.indexOf(END, s + START.length());
if (e < 0) {
// 起始存在但尚无结束,保留从起始开始的内容,丢弃起始前的噪声
if (s > 0) sb.delete(0, s);
break;
}
// 帧内容为 [s+START.length, e)
int payloadStart = s + START.length();
String json = sb.substring(payloadStart, e).trim();
if (!identifierReceived) {
// 解析首个帧作为设备标识符
clientId = json;
ClientConnection clientConnection = clientMaps.get(clientId);
if (ObjectUtil.isNotNull(clientConnection) && ObjectUtil.isNotNull(clientConnection.getSocket())) {
log.info("客户端连接重复: {},断开旧连接。", clientId);
clientConnection.getSocket().close(); //有重复连接,断开旧连接
}else {
clientConnection = new ClientConnection();
}
clientConnection.setSocket(clientSocket);
clientConnection.setConnectTime(System.currentTimeMillis());
clientConnection.setLastActiveTime(System.currentTimeMillis());
clientMaps.put(clientId, clientConnection); // 关联标识符与Socket
log.info("新客户端连接: {}", clientId);
identifierReceived = true;
continue;
}
// 若尾部含其他字符,兜底裁到最后一个 '}'
int lastBrace = json.lastIndexOf('}');
if (lastBrace >= 0) {
json = json.substring(0, lastBrace + 1);
}
// 检查是否是心跳包
if (HEARTBEAT.equals(json.trim())) {
log.info("收到客户端 {} 的心跳包", clientId);
// 更新客户端最后活动时间
ClientConnection clientConnection = clientMaps.get(clientId);
clientConnection.setLastActiveTime(System.currentTimeMillis());
// 发送心跳响应
String finalClientId = clientId;
heartbeatService.schedule(() -> writeFrame(finalClientId, "heartbeat_ack", charset), 100, TimeUnit.MILLISECONDS);
sb.delete(0, e + END.length());
continue;
}else {
log.info("接收到帧: {}", json);
try {
// 解析成功后,封装并发送温湿度响应
try {
JsonNode root = objectMapper.readTree(json);
log.info("解析数据成功:{}",root);
} catch (Exception sendEx) {
log.warn("构造/发送温湿度响应失败: {}", sendEx.getMessage());
}
} catch (Exception ex) {
log.error("解析传感器数据失败: {}", json, ex);
}
}
// 删除到结束标记之后,继续解析后续帧
sb.delete(0, e + END.length());
}
}
// 连接关闭时的尾帧兜底
int s = sb.indexOf(START);
int e = (s >= 0) ? sb.indexOf(END, s + START.length()) : -1;
if (s >= 0 && e > s) {
String json = sb.substring(s + START.length(), e).trim();
int lastBrace = json.lastIndexOf('}');
if (lastBrace >= 0) json = json.substring(0, lastBrace + 1);
if (!json.isEmpty()) {
log.info("接收到尾帧: {}", json);
try {
} catch (Exception ex) {
log.error("解析传感器数据失败(尾帧): {}", json, ex);
}
}
}
} catch (IOException e) {
log.error("处理客户端连接时发生错误", e);
} finally {
if (clientId != null) {
clientMaps.remove(clientId);
}
try {
// 客户端断开连接时执行的代码
clientSocket.close();
log.info("客户端连接已关闭: {}", clientId);
} catch (IOException e) {
log.error("关闭客户端连接时发生错误", e);
}
}
}
/**
*
*/
private void checkHeartbeats() {
long currentTime = System.currentTimeMillis();
long timeout = 90 * 1000; // 90秒超时
clientMaps.entrySet().removeIf(entry -> {
String clientId = entry.getKey();
ClientConnection clientConnection = entry.getValue();
if (ObjectUtil.isNotNull(clientConnection) && ObjectUtil.isNotNull(clientConnection.getLastActiveTime())) {
// 检查是否超时
if (currentTime - clientConnection.getLastActiveTime() > timeout) {
log.warn("客户端 {} 心跳超时,断开连接", clientId);
try {
clientConnection.getSocket().close();
} catch (IOException e) {
log.error("关闭超时客户端连接时发生错误", e);
}
return true; // 移除该客户端
}
return false; // 保留该客户端
}else{
return true;
}
});
}
/**
* hellohello + json + byebye
*/
private void writeFrame(String clientId, String json, Charset charset) {
String frame = "hellohello" + json + "byebye";
try {
ClientConnection clientConnection = clientMaps.get(clientId);
if (ObjectUtil.isNotNull(clientConnection)) {
clientConnection.getSocket().getOutputStream().write(frame.getBytes(charset));
clientConnection.getSocket().getOutputStream().flush();
}
log.info("向客户端 {} 发送帧:{}", clientId, frame);
} catch (IOException e) {
log.warn("向客户端 {} 发送帧失败: {}", clientId, e.getMessage());
}
}
/**
*
* @param clientId
* @param message
* @throws IOException IO
*/
public void sendDataToClient(String clientId, String message) throws IOException {
ClientConnection clientConnection = clientMaps.get(clientId);
if (ObjectUtil.isNotNull(clientConnection) && clientConnection.getSocket() != null && !clientConnection.getSocket().isClosed()) {
writeFrame(clientId, message, StandardCharsets.UTF_8);
} else {
log.warn("无法向客户端 {} 发送数据因为找不到对应的Socket或已关闭", clientId);
}
}
/**
* Socket
*/
private void configureClientSocket(Socket socket) {
try {
socket.setKeepAlive(true);
socket.setTcpNoDelay(true);
// 设置读超时,避免永久阻塞
// socket.setSoTimeout(60000); // 60秒超时
} catch (Exception e) {
log.warn("配置客户端Socket参数失败: {}", e.getMessage());
}
}
/**
* TCP
*/
@PreDestroy
public void stopTcpServer() {
try {
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close();
log.info("TCP服务器已停止");
}
executorService.shutdown();
heartbeatService.shutdown();
started.set(false);
} catch (IOException e) {
log.error("停止TCP服务器时发生错误", e);
}
}
}

@ -59,25 +59,25 @@
<version>${anyline.version}</version>
</dependency>
<dependency>
<groupId>org.anyline</groupId>
<artifactId>anyline-data-jdbc-mysql</artifactId>
<version>${anyline.version}</version>
</dependency>
<!-- anyline支持100+种类型数据库 添加对应的jdbc依赖与anyline对应数据库依赖包即可 -->
<!-- <dependency>-->
<!-- <groupId>org.anyline</groupId>-->
<!-- <artifactId>anyline-data-jdbc-oracle</artifactId>-->
<!-- <artifactId>anyline-data-jdbc-mysql</artifactId>-->
<!-- <version>${anyline.version}</version>-->
<!-- </dependency>-->
<!-- anyline支持100+种类型数据库 添加对应的jdbc依赖与anyline对应数据库依赖包即可 -->
<!-- <dependency>-->
<!-- <groupId>org.anyline</groupId>-->
<!-- <artifactId>anyline-data-jdbc-postgresql</artifactId>-->
<!-- <artifactId>anyline-data-jdbc-oracle</artifactId>-->
<!-- <version>${anyline.version}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.anyline</groupId>
<artifactId>anyline-data-jdbc-postgresql</artifactId>
<version>${anyline.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.anyline</groupId>-->
<!-- <artifactId>anyline-data-jdbc-mssql</artifactId>-->

@ -321,7 +321,7 @@ public class GenTableServiceImpl implements IGenTableService {
// 查询表信息
GenTable table = baseMapper.selectGenTableById(tableId);
List<Long> menuIds = new ArrayList<>();
for (int i = 0; i < 6; i++) {
for (int i = 0; i < 7; i++) {
menuIds.add(identifierGenerator.nextId(null).longValue());
}
table.setMenuIds(menuIds);
@ -467,7 +467,7 @@ public class GenTableServiceImpl implements IGenTableService {
// 查询表信息
GenTable table = baseMapper.selectGenTableById(tableId);
List<Long> menuIds = new ArrayList<>();
for (int i = 0; i < 6; i++) {
for (int i = 0; i < 7; i++) {
menuIds.add(identifierGenerator.nextId(null).longValue());
}
table.setMenuIds(menuIds);

@ -1,8 +1,11 @@
-- 菜单 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, sysdate, null, null, '${functionName}菜单');
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '', '#', 103, 1, sysdate, null, null, '${functionName}菜单');
-- 按钮 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:list', '#', 103, 1, sysdate, null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, sysdate, null, null, '');

@ -1,20 +1,23 @@
-- 菜单 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, now(), null, null, '${functionName}菜单');
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '', '#', 103, 1, now(), null, null, '${functionName}菜单');
-- 按钮 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, now(), null, null, '');
values(${table.menuIds[1]}, '${functionName}列表', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:list', '#', 103, 1, now(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, now(), null, null, '');
values(${table.menuIds[2]}, '${functionName}查询', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, now(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, now(), null, null, '');
values(${table.menuIds[3]}, '${functionName}新增', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, now(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, now(), null, null, '');
values(${table.menuIds[4]}, '${functionName}修改', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, now(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, now(), null, null, '');
values(${table.menuIds[5]}, '${functionName}删除', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, now(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[6]}, '${functionName}导出', ${table.menuIds[0]}, '6', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, now(), null, null, '');

@ -1,19 +1,22 @@
-- 菜单 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, sysdate(), null, null, '${functionName}菜单');
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '', '#', 103, 1, sysdate(), null, null, '${functionName}菜单');
-- 按钮 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, sysdate(), null, null, '');
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:list', '#', 103, 1, sysdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, sysdate(), null, null, '');
values(${table.menuIds[2]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, sysdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, sysdate(), null, null, '');
values(${table.menuIds[3]}, '${functionName}新增', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, sysdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, sysdate(), null, null, '');
values(${table.menuIds[4]}, '${functionName}修改', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, sysdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, sysdate(), null, null, '');
values(${table.menuIds[5]}, '${functionName}删除', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, sysdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[6]}, '${functionName}导出', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, sysdate(), null, null, '');

@ -1,19 +1,22 @@
-- 菜单 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, getdate(), null, null, '${functionName}菜单');
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '', '#', 103, 1, getdate(), null, null, '${functionName}菜单');
-- 按钮 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, getdate(), null, null, '');
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:list', '#', 103, 1, getdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, getdate(), null, null, '');
values(${table.menuIds[2]}, '${functionName}查询', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, getdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, getdate(), null, null, '');
values(${table.menuIds[3]}, '${functionName}新增', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, getdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, getdate(), null, null, '');
values(${table.menuIds[4]}, '${functionName}修改', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, getdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, getdate(), null, null, '');
values(${table.menuIds[5]}, '${functionName}删除', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, getdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[6]}, '${functionName}导出', ${table.menuIds[0]}, '6', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, getdate(), null, null, '');

@ -67,7 +67,7 @@ public class SysProfileController extends BaseController {
SysUserBo user = BeanUtil.toBean(profile, SysUserBo.class);
user.setUserId(LoginHelper.getUserId());
String username = LoginHelper.getUsername();
if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user.getPhonenumber())) {
return R.fail("修改用户'" + username + "'失败,手机号码已存在");
}
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {

@ -24,6 +24,7 @@ import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.service.ISysTenantService;
import org.dromara.system.service.ISysUserService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -43,6 +44,7 @@ import java.util.List;
public class SysTenantController extends BaseController {
private final ISysTenantService tenantService;
private final ISysUserService userService;
/**
*
@ -93,6 +95,13 @@ public class SysTenantController extends BaseController {
if (!tenantService.checkCompanyNameUnique(bo)) {
return R.fail("新增租户'" + bo.getCompanyName() + "'失败,企业名称已存在");
}
// 校验用户名和手机号是否全局唯一(方法内部已使用TenantHelper.ignore)
if (!userService.checkUserNameUnique(bo.getUsername())) {
return R.fail("新增租户失败,管理员账号'" + bo.getUsername() + "'已存在");
}
if (!userService.checkPhoneUnique(bo.getContactPhone())) {
return R.fail("新增租户失败,联系电话'" + bo.getContactPhone() + "'已存在");
}
return toAjax(TenantHelper.ignore(() -> tenantService.insertByBo(bo)));
}
@ -109,6 +118,9 @@ public class SysTenantController extends BaseController {
if (!tenantService.checkCompanyNameUnique(bo)) {
return R.fail("修改租户'" + bo.getCompanyName() + "'失败,公司名称已存在");
}
if (!userService.checkPhoneUnique(bo.getContactPhone())) {
return R.fail("新增租户失败,联系电话'" + bo.getContactPhone() + "'已存在");
}
return toAjax(tenantService.updateByBo(bo));
}

@ -161,9 +161,9 @@ public class SysUserController extends BaseController {
@PostMapping
public R<Void> add(@Validated @RequestBody SysUserBo user) {
deptService.checkDeptDataScope(user.getDeptId());
if (!userService.checkUserNameUnique(user)) {
if (!userService.checkUserNameUnique(user.getUserName())) {
return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user.getPhonenumber())) {
return R.fail("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
} else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
@ -188,9 +188,9 @@ public class SysUserController extends BaseController {
userService.checkUserAllowed(user.getUserId());
userService.checkUserDataScope(user.getUserId());
deptService.checkDeptDataScope(user.getDeptId());
if (!userService.checkUserNameUnique(user)) {
if (!userService.checkUserNameUnique(user.getUserName())) {
return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user.getPhonenumber())) {
return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
} else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");

@ -25,6 +25,11 @@ public interface ISysTenantService {
*/
SysTenantVo queryByTenantId(String tenantId);
/**
*
*/
SysTenantVo queryByUsername(String username);
/**
*
*/

@ -103,18 +103,18 @@ public interface ISysUserService {
/**
*
*
* @param user
* @param username
* @return
*/
boolean checkUserNameUnique(SysUserBo user);
boolean checkUserNameUnique(String username);
/**
*
*
* @param user
* @param phone
* @return
*/
boolean checkPhoneUnique(SysUserBo user);
boolean checkPhoneUnique(String phone);
/**
* email

@ -28,8 +28,10 @@ import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.*;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.*;
import org.dromara.system.service.ISysTenantService;
import org.dromara.system.service.ISysUserService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@ -49,6 +51,7 @@ public class SysTenantServiceImpl implements ISysTenantService {
private final SysTenantMapper baseMapper;
private final SysTenantPackageMapper tenantPackageMapper;
private final SysUserMapper userMapper;
private final ISysUserService userService;
private final SysDeptMapper deptMapper;
private final SysRoleMapper roleMapper;
private final SysRoleMenuMapper roleMenuMapper;
@ -75,6 +78,22 @@ public class SysTenantServiceImpl implements ISysTenantService {
return baseMapper.selectVoOne(new LambdaQueryWrapper<SysTenant>().eq(SysTenant::getTenantId, tenantId));
}
/**
*
*/
@Cacheable(cacheNames = CacheNames.SYS_TENANT, key = "#username")
@Override
public SysTenantVo queryByUsername(String username) {
SysUserVo sysUserVo = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(sysUserVo)) {
sysUserVo = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, username));
}
if (ObjectUtil.isNotNull(sysUserVo)) {
return baseMapper.selectVoOne(new LambdaQueryWrapper<SysTenant>().eq(SysTenant::getTenantId, sysUserVo.getTenantId()));
}
return null;
}
/**
*
*/
@ -158,6 +177,7 @@ public class SysTenantServiceImpl implements ISysTenantService {
user.setNickName(bo.getUsername());
user.setPassword(BCrypt.hashpw(bo.getPassword()));
user.setDeptId(deptId);
user.setPhonenumber(bo.getContactPhone());
userMapper.insert(user);
//新增系统用户后,默认当前用户为部门的负责人
SysDept sd = new SysDept();
@ -325,6 +345,19 @@ public class SysTenantServiceImpl implements ISysTenantService {
throw new ServiceException("超管租户不能删除");
}
}
// 删除租户前,先查询出租户编号(String类型),再删除对应用户
List<String> tenantIds = baseMapper.selectList(
new LambdaQueryWrapper<SysTenant>()
.select(SysTenant::getTenantId)
.in(SysTenant::getId, ids)
).stream().map(SysTenant::getTenantId).toList();
if (CollUtil.isNotEmpty(tenantIds)) {
TenantHelper.ignore(() -> {
userMapper.delete(new LambdaQueryWrapper<SysUser>().in(SysUser::getTenantId, tenantIds));
});
}
return baseMapper.deleteByIds(ids) > 0;
}

@ -22,6 +22,7 @@ import org.dromara.common.core.utils.*;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.SysUserPost;
import org.dromara.system.domain.SysUserRole;
@ -38,6 +39,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
/**
*
@ -234,28 +236,33 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
/**
*
*
* @param user
* @param username
* @return
*/
@Override
public boolean checkUserNameUnique(SysUserBo user) {
boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUserName, user.getUserName())
.ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
return !exist;
public boolean checkUserNameUnique(String username) {
AtomicBoolean exist = new AtomicBoolean(false);
TenantHelper.ignore(() -> {
exist.set(baseMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUserName, username)));
});
return !exist.get();
}
/**
*
*
* @param user
* @param phonenumber
*/
@Override
public boolean checkPhoneUnique(SysUserBo user) {
boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getPhonenumber, user.getPhonenumber())
.ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
return !exist;
public boolean checkPhoneUnique(String phonenumber) {
AtomicBoolean exist = new AtomicBoolean(false);
TenantHelper.ignore(() -> {
exist.set(baseMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getPhonenumber, phonenumber)));
});
return !exist.get();
}
/**

Loading…
Cancel
Save