From 17ede93a04861e9306eb45965fc052c8ebb70834 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Mon, 12 Oct 2026 13:49:08 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=96=B0=E5=A2=9E=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=94=A8=E6=88=B7=E5=90=8E=E5=8F=B0=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=8E=A5=E5=8F=A3=E4=B8=8E=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../springblade/system/pojo/vo/MpUserVO.java | 73 ++++++++++ .../system/controller/MpUserController.java | 134 ++++++++++++++++++ .../system/service/IMpUserService.java | 29 ++++ .../service/impl/MpUserServiceImpl.java | 108 ++++++++++++++ .../system/wrapper/MpUserWrapper.java | 78 ++++++++++ .../transport/blade_mp_user_menu_20261012.sql | 90 ++++++++++++ 6 files changed, 512 insertions(+) create mode 100644 blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/vo/MpUserVO.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/controller/MpUserController.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/wrapper/MpUserWrapper.java create mode 100644 doc/sql/transport/blade_mp_user_menu_20261012.sql diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/vo/MpUserVO.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/vo/MpUserVO.java new file mode 100644 index 00000000..c2ced763 --- /dev/null +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/vo/MpUserVO.java @@ -0,0 +1,73 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.vo; + +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.system.pojo.entity.MpUser; + +import java.io.Serial; + +/** + * 小程序用户视图实体类 + * + * @author Chill + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "小程序用户") +public class MpUserVO extends MpUser { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 身份名称(逗号分隔中文) + */ + @TableField(exist = false) + @Schema(description = "身份名称") + private String identitiesName; + + /** + * 性别名称 + */ + @TableField(exist = false) + @Schema(description = "性别名称") + private String sexName; + + /** + * 创建人姓名 + */ + @TableField(exist = false) + @Schema(description = "创建人姓名") + private String createUserName; + + /** + * 更新人姓名 + */ + @TableField(exist = false) + @Schema(description = "更新人姓名") + private String updateUserName; + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/MpUserController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/MpUserController.java new file mode 100644 index 00000000..b4c84f8e --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/MpUserController.java @@ -0,0 +1,134 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.mp.support.Query; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.tenant.annotation.NonDS; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.pojo.entity.MpUser; +import org.springblade.system.pojo.vo.MpUserVO; +import org.springblade.system.service.IMpUserService; +import org.springblade.system.wrapper.MpUserWrapper; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * 小程序用户管理 控制器 + * + * @author Chill + */ +@NonDS +@RestController +@AllArgsConstructor +@PreAuth(menu = "mp_user") +@RequestMapping("mp-user") +@Tag(name = "小程序用户", description = "小程序用户管理") +public class MpUserController extends BladeController { + + private final IMpUserService mpUserService; + + /** + * 详情 + */ + @GetMapping("/detail") + @ApiOperationSupport(order = 1) + @Operation(summary = "详情", description = "传入id") + public R detail(MpUser mpUser) { + MpUser detail = mpUserService.getOne(Condition.getQueryWrapper(mpUser)); + if (detail == null) { + return R.fail("小程序用户不存在"); + } + return R.data(MpUserWrapper.build().entityVO(detail)); + } + + /** + * 分页 + */ + @GetMapping("/list") + @ApiOperationSupport(order = 2) + @Operation(summary = "分页", description = "传入查询条件") + public R> list(MpUser mpUser, Query query) { + IPage pages = mpUserService.selectMpUserPage(Condition.getPage(query), mpUser); + return R.data(MpUserWrapper.build().pageVO(pages)); + } + + /** + * 新增或修改 + */ + @PostMapping("/submit") + @ApiOperationSupport(order = 3) + @Operation(summary = "新增或修改", description = "传入MpUser") + public R submit(@Valid @RequestBody MpUser mpUser) { + return R.status(mpUserService.submitOrUpdate(mpUser)); + } + + /** + * 逻辑删除 + */ + @PostMapping("/remove") + @ApiOperationSupport(order = 4) + @Operation(summary = "逻辑删除", description = "传入ids") + public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { + return R.status(mpUserService.removeMpUser(Func.toLongList(ids))); + } + + /** + * 启用 / 停用 + */ + @PostMapping("/change-status") + @ApiOperationSupport(order = 5) + @Operation(summary = "修改状态", description = "status:1启用,0停用") + public R changeStatus(@Parameter(description = "用户主键", required = true) @RequestParam Long id, + @Parameter(description = "状态:1启用,0停用", required = true) @RequestParam Integer status) { + return R.status(mpUserService.changeStatus(id, status)); + } + + /** + * 重置密码 + */ + @PostMapping("/reset-password") + @ApiOperationSupport(order = 6) + @Operation(summary = "重置密码", description = "传入userId集合,返回账号与明文密码") + public R> resetPassword( + @Parameter(description = "userId集合", required = true) @RequestParam String userIds) { + return R.data(mpUserService.resetPassword(Func.toLongList(userIds))); + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IMpUserService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IMpUserService.java index f05932ea..c0294ced 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IMpUserService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IMpUserService.java @@ -22,10 +22,14 @@ */ package org.springblade.system.service; +import com.baomidou.mybatisplus.core.metadata.IPage; import org.springblade.core.mp.base.BaseService; import org.springblade.system.pojo.entity.MpUser; import org.springblade.system.pojo.entity.MpUserInfo; +import java.util.List; +import java.util.Map; + /** * 小程序用户 服务类 * @@ -58,6 +62,11 @@ public interface IMpUserService extends BaseService { */ MpUserInfo userInfoByAccount(String tenantId, String account); + /** + * 后台分页查询 + */ + IPage selectMpUserPage(IPage page, MpUser query); + /** * 保存小程序用户 */ @@ -68,6 +77,26 @@ public interface IMpUserService extends BaseService { */ boolean updateUser(MpUser user); + /** + * 新增或修改(后台管理) + */ + boolean submitOrUpdate(MpUser user); + + /** + * 逻辑删除 + */ + boolean removeMpUser(List ids); + + /** + * 启用 / 停用 + */ + boolean changeStatus(Long id, Integer status); + + /** + * 重置密码为随机密码,返回 account -> 明文密码 + */ + Map resetPassword(List userIds); + /** * 绑定/更新 openid */ diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MpUserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MpUserServiceImpl.java index da400e69..93f744ba 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MpUserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MpUserServiceImpl.java @@ -22,11 +22,13 @@ */ package org.springblade.system.service.impl; +import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.springblade.common.utils.PasswordRuleUtil; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; +import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.support.Kv; import org.springblade.core.tool.utils.DateUtil; import org.springblade.core.tool.utils.DigestUtil; @@ -41,7 +43,9 @@ import org.springblade.system.service.IMpUserService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -95,6 +99,19 @@ public class MpUserServiceImpl extends BaseServiceImpl imp return buildUserInfo(getByAccount(tenantId, account)); } + @Override + public IPage selectMpUserPage(IPage page, MpUser query) { + MpUser condition = query == null ? new MpUser() : query; + return page(page, Wrappers.lambdaQuery() + .like(Func.isNotBlank(condition.getPhone()), MpUser::getPhone, condition.getPhone()) + .like(Func.isNotBlank(condition.getAccount()), MpUser::getAccount, condition.getAccount()) + .like(Func.isNotBlank(condition.getRealName()), MpUser::getRealName, condition.getRealName()) + .like(Func.isNotBlank(condition.getName()), MpUser::getName, condition.getName()) + .like(Func.isNotBlank(condition.getIdentities()), MpUser::getIdentities, condition.getIdentities()) + .eq(condition.getStatus() != null, MpUser::getStatus, condition.getStatus()) + .orderByDesc(MpUser::getCreateTime)); + } + @Override @Transactional(rollbackFor = Exception.class) public boolean submit(MpUser user) { @@ -104,6 +121,7 @@ public class MpUserServiceImpl extends BaseServiceImpl imp if (existed != null && Func.isNotEmpty(existed.getId())) { throw new ServiceException("该手机号已存在小程序用户"); } + encryptPasswordIfPresent(user); boolean saved = save(user); if (saved) { MpUserCache.clear(); @@ -123,6 +141,8 @@ public class MpUserServiceImpl extends BaseServiceImpl imp if (existed != null && Func.isNotEmpty(existed.getId()) && !existed.getId().equals(user.getId())) { throw new ServiceException("该手机号已存在小程序用户"); } + // 后台编辑不改密码,避免空串覆盖 + user.setPassword(null); boolean updated = updateById(user); if (updated) { MpUserCache.clear(); @@ -130,6 +150,79 @@ public class MpUserServiceImpl extends BaseServiceImpl imp return updated; } + @Override + @Transactional(rollbackFor = Exception.class) + public boolean submitOrUpdate(MpUser user) { + if (user != null && Func.isNotEmpty(user.getId())) { + return updateUser(user); + } + return submit(user); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean removeMpUser(List ids) { + if (Func.isEmpty(ids)) { + throw new ServiceException("请选择要删除的小程序用户"); + } + boolean removed = deleteLogic(ids); + if (removed) { + MpUserCache.clear(); + } + return removed; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean changeStatus(Long id, Integer status) { + if (Func.isEmpty(id)) { + throw new ServiceException("小程序用户主键不能为空"); + } + if (status == null || (status != 0 && status != 1)) { + throw new ServiceException("状态仅支持启用(1)或停用(0)"); + } + MpUser user = getById(id); + if (user == null || Func.isEmpty(user.getId())) { + throw new ServiceException("小程序用户不存在"); + } + MpUser patch = new MpUser(); + patch.setId(id); + patch.setStatus(status); + boolean updated = updateById(patch); + if (updated) { + MpUserCache.clear(); + } + return updated; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Map resetPassword(List userIds) { + if (Func.isEmpty(userIds)) { + throw new ServiceException("请选择要重置密码的小程序用户"); + } + Map passwordMap = new LinkedHashMap<>(); + for (Long userId : userIds) { + MpUser user = getById(userId); + if (user == null || Func.isEmpty(user.getId())) { + continue; + } + String password = PasswordRuleUtil.generate(); + MpUser patch = new MpUser(); + patch.setId(userId); + patch.setPassword(DigestUtil.encrypt(password)); + patch.setUpdateTime(DateUtil.now()); + updateById(patch); + String account = Func.toStr(user.getAccount(), user.getPhone()); + passwordMap.put(account, password); + } + if (passwordMap.isEmpty()) { + throw new ServiceException("未找到可重置密码的小程序用户"); + } + MpUserCache.clear(); + return passwordMap; + } + @Override @Transactional(rollbackFor = Exception.class) public boolean bindOpenId(String tenantId, Long userId, String openid) { @@ -207,6 +300,9 @@ public class MpUserServiceImpl extends BaseServiceImpl imp if (user == null) { return; } + if (Func.isBlank(user.getTenantId())) { + user.setTenantId(AuthUtil.getTenantId()); + } user.setAccount(trimToNull(user.getAccount())); user.setPhone(trimToNull(user.getPhone())); user.setName(trimToNull(user.getName())); @@ -229,6 +325,18 @@ public class MpUserServiceImpl extends BaseServiceImpl imp } } + private void encryptPasswordIfPresent(MpUser user) { + if (user == null || Func.isBlank(user.getPassword())) { + user.setPassword(null); + return; + } + String password = user.getPassword().trim(); + if (!PasswordRuleUtil.isValid(password)) { + throw new ServiceException(PasswordRuleUtil.RULE_MESSAGE); + } + user.setPassword(DigestUtil.encrypt(password)); + } + private void validate(MpUser user) { if (user == null) { throw new ServiceException("小程序用户不能为空"); diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/MpUserWrapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/MpUserWrapper.java new file mode 100644 index 00000000..2022dcd7 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/MpUserWrapper.java @@ -0,0 +1,78 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.wrapper; + +import org.springblade.core.mp.support.BaseEntityWrapper; +import org.springblade.core.tool.utils.BeanUtil; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.cache.DictCache; +import org.springblade.system.cache.UserCache; +import org.springblade.system.pojo.entity.MpUser; +import org.springblade.system.pojo.enums.DictEnum; +import org.springblade.system.pojo.enums.MpUserIdentity; +import org.springblade.system.pojo.vo.MpUserVO; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 小程序用户包装类 + * + * @author Chill + */ +public class MpUserWrapper extends BaseEntityWrapper { + + public static MpUserWrapper build() { + return new MpUserWrapper(); + } + + @Override + public MpUserVO entityVO(MpUser mpUser) { + MpUserVO mpUserVO = Objects.requireNonNull(BeanUtil.copyProperties(mpUser, MpUserVO.class)); + // 密码哈希不下发前端 + mpUserVO.setPassword(null); + mpUserVO.setIdentitiesName(resolveIdentitiesName(mpUser.getIdentities())); + if (mpUser.getSex() != null) { + mpUserVO.setSexName(DictCache.getValue(DictEnum.SEX, mpUser.getSex())); + } + mpUserVO.setCreateUserName(UserCache.getUserRealName(mpUser.getCreateUser())); + mpUserVO.setUpdateUserName(UserCache.getUserRealName(mpUser.getUpdateUser())); + return mpUserVO; + } + + private String resolveIdentitiesName(String identities) { + List identityList = MpUserIdentity.split(identities); + if (identityList.isEmpty()) { + return ""; + } + return identityList.stream() + .map(code -> { + MpUserIdentity identity = MpUserIdentity.of(code); + return identity == null ? code : identity.getLabel(); + }) + .filter(Func::isNotBlank) + .collect(Collectors.joining(",")); + } + +} diff --git a/doc/sql/transport/blade_mp_user_menu_20261012.sql b/doc/sql/transport/blade_mp_user_menu_20261012.sql new file mode 100644 index 00000000..c918cd6b --- /dev/null +++ b/doc/sql/transport/blade_mp_user_menu_20261012.sql @@ -0,0 +1,90 @@ +-- ---------------------------- +-- 小程序用户管理菜单(挂在系统管理下) +-- 日期:2026-10-12 +-- ---------------------------- + +INSERT INTO `blade_menu` + (`id`,`parent_id`,`code`,`name`,`alias`,`path`,`source`,`sort`,`category`,`action`,`is_open`,`component`,`remark`,`is_deleted`) +VALUES + (2091901200000000101,1123598815738675203,'mp_user','小程序用户','menu','/system/mp-user','iconfont icon-yonghu',3,1,0,1,NULL,'小程序用户管理',0), + (2091901200000000102,2091901200000000101,'mp_user_add','新增','add','','',1,2,0,1,NULL,'',0), + (2091901200000000103,2091901200000000101,'mp_user_edit','编辑','edit','','',2,2,0,1,NULL,'',0), + (2091901200000000104,2091901200000000101,'mp_user_delete','删除','delete','','',3,2,0,1,NULL,'',0), + (2091901200000000105,2091901200000000101,'mp_user_view','查看','view','','',4,2,0,1,NULL,'',0), + (2091901200000000106,2091901200000000101,'mp_user_status','修改状态','status','','',5,2,0,1,NULL,'',0), + (2091901200000000107,2091901200000000101,'mp_user_reset','密码重置','reset','','',6,2,0,1,NULL,'',0) +ON DUPLICATE KEY UPDATE + `parent_id`=VALUES(`parent_id`), + `code`=VALUES(`code`), + `name`=VALUES(`name`), + `alias`=VALUES(`alias`), + `path`=VALUES(`path`), + `source`=VALUES(`source`), + `sort`=VALUES(`sort`), + `category`=VALUES(`category`), + `remark`=VALUES(`remark`), + `is_deleted`=0; + +-- 授权给超级管理员角色(幂等) +INSERT INTO `blade_role_menu` (`id`, `menu_id`, `role_id`) +SELECT 2091901200000000101 + (r.`id` % 1000), 2091901200000000101, r.`id` +FROM `blade_role` r +WHERE r.`role_alias` = 'administrator' + AND NOT EXISTS ( + SELECT 1 FROM `blade_role_menu` x + WHERE x.`menu_id` = 2091901200000000101 AND x.`role_id` = r.`id` + ); + +INSERT INTO `blade_role_menu` (`id`, `menu_id`, `role_id`) +SELECT 2091901200000000102 + (r.`id` % 1000), 2091901200000000102, r.`id` +FROM `blade_role` r +WHERE r.`role_alias` = 'administrator' + AND NOT EXISTS ( + SELECT 1 FROM `blade_role_menu` x + WHERE x.`menu_id` = 2091901200000000102 AND x.`role_id` = r.`id` + ); + +INSERT INTO `blade_role_menu` (`id`, `menu_id`, `role_id`) +SELECT 2091901200000000103 + (r.`id` % 1000), 2091901200000000103, r.`id` +FROM `blade_role` r +WHERE r.`role_alias` = 'administrator' + AND NOT EXISTS ( + SELECT 1 FROM `blade_role_menu` x + WHERE x.`menu_id` = 2091901200000000103 AND x.`role_id` = r.`id` + ); + +INSERT INTO `blade_role_menu` (`id`, `menu_id`, `role_id`) +SELECT 2091901200000000104 + (r.`id` % 1000), 2091901200000000104, r.`id` +FROM `blade_role` r +WHERE r.`role_alias` = 'administrator' + AND NOT EXISTS ( + SELECT 1 FROM `blade_role_menu` x + WHERE x.`menu_id` = 2091901200000000104 AND x.`role_id` = r.`id` + ); + +INSERT INTO `blade_role_menu` (`id`, `menu_id`, `role_id`) +SELECT 2091901200000000105 + (r.`id` % 1000), 2091901200000000105, r.`id` +FROM `blade_role` r +WHERE r.`role_alias` = 'administrator' + AND NOT EXISTS ( + SELECT 1 FROM `blade_role_menu` x + WHERE x.`menu_id` = 2091901200000000105 AND x.`role_id` = r.`id` + ); + +INSERT INTO `blade_role_menu` (`id`, `menu_id`, `role_id`) +SELECT 2091901200000000106 + (r.`id` % 1000), 2091901200000000106, r.`id` +FROM `blade_role` r +WHERE r.`role_alias` = 'administrator' + AND NOT EXISTS ( + SELECT 1 FROM `blade_role_menu` x + WHERE x.`menu_id` = 2091901200000000106 AND x.`role_id` = r.`id` + ); + +INSERT INTO `blade_role_menu` (`id`, `menu_id`, `role_id`) +SELECT 2091901200000000107 + (r.`id` % 1000), 2091901200000000107, r.`id` +FROM `blade_role` r +WHERE r.`role_alias` = 'administrator' + AND NOT EXISTS ( + SELECT 1 FROM `blade_role_menu` x + WHERE x.`menu_id` = 2091901200000000107 AND x.`role_id` = r.`id` + );