✨ 新增小程序用户后台管理接口与菜单

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-12 13:49:08 +08:00
parent e19c3b100a
commit 17ede93a04
6 changed files with 512 additions and 0 deletions
@@ -0,0 +1,73 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* 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;
}
@@ -0,0 +1,134 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* 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<MpUserVO> 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<IPage<MpUserVO>> list(MpUser mpUser, Query query) {
IPage<MpUser> 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<Map<String, String>> resetPassword(
@Parameter(description = "userId集合", required = true) @RequestParam String userIds) {
return R.data(mpUserService.resetPassword(Func.toLongList(userIds)));
}
}
@@ -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<MpUser> {
*/
MpUserInfo userInfoByAccount(String tenantId, String account);
/**
* 后台分页查询
*/
IPage<MpUser> selectMpUserPage(IPage<MpUser> page, MpUser query);
/**
* 保存小程序用户
*/
@@ -68,6 +77,26 @@ public interface IMpUserService extends BaseService<MpUser> {
*/
boolean updateUser(MpUser user);
/**
* 新增或修改(后台管理)
*/
boolean submitOrUpdate(MpUser user);
/**
* 逻辑删除
*/
boolean removeMpUser(List<Long> ids);
/**
* 启用 / 停用
*/
boolean changeStatus(Long id, Integer status);
/**
* 重置密码为随机密码,返回 account -> 明文密码
*/
Map<String, String> resetPassword(List<Long> userIds);
/**
* 绑定/更新 openid
*/
@@ -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<MpUserMapper, MpUser> imp
return buildUserInfo(getByAccount(tenantId, account));
}
@Override
public IPage<MpUser> selectMpUserPage(IPage<MpUser> page, MpUser query) {
MpUser condition = query == null ? new MpUser() : query;
return page(page, Wrappers.<MpUser>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<MpUserMapper, MpUser> 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<MpUserMapper, MpUser> 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<MpUserMapper, MpUser> 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<Long> 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<String, String> resetPassword(List<Long> userIds) {
if (Func.isEmpty(userIds)) {
throw new ServiceException("请选择要重置密码的小程序用户");
}
Map<String, String> 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<MpUserMapper, MpUser> 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<MpUserMapper, MpUser> 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("小程序用户不能为空");
@@ -0,0 +1,78 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* 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<MpUser, MpUserVO> {
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<String> 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(","));
}
}
@@ -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`
);