diff --git a/sql/mysql/member/member_ride_record_mock.py b/sql/mysql/member/member_ride_record_mock.py new file mode 100644 index 0000000..9a1b3fd --- /dev/null +++ b/sql/mysql/member/member_ride_record_mock.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""骑遇骑行记录:建表 + 模拟数据(chat_v1 库)。可重复执行(幂等:表已存在跳过,模拟数据按标题去重)。""" +import json +import math +import pymysql + +DB = dict(host='139.224.25.207', port=3306, user='chat_v1', + password='BpJ2Ebt4zMPjBcCL', database='chat_v1', charset='utf8mb4', + connect_timeout=15) + +DDL = [ + """ + CREATE TABLE IF NOT EXISTS `member_ride_record` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint(20) NOT NULL COMMENT '骑手编号 member_user.id', + `title` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '战报标题', + `road_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '路线名(如:南昆山)', + `corner_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '弯道名(如:观音潭右弯),空表示非跑山记录', + `distance_km` decimal(8,1) NOT NULL COMMENT '里程(公里)', + `duration_sec` int(11) NOT NULL COMMENT '骑行时长(秒)', + `top_speed_kmh` decimal(5,1) NOT NULL COMMENT '极速(km/h)', + `curve_count` int(11) NOT NULL DEFAULT '0' COMMENT '过弯次数', + `max_lean` decimal(4,1) NOT NULL COMMENT '最大压弯角度(度)', + `track_json` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '轨迹点 JSON [{lat,lng,speed}],仅详情接口返回', + `like_count` int(11) NOT NULL DEFAULT '0' COMMENT '点赞数(冗余计数)', + `report_count` int(11) NOT NULL DEFAULT '0' COMMENT '被举报次数', + `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0 正常 1 已下架', + `tenant_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '租户编号', + `creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + KEY `idx_user` (`user_id`), + KEY `idx_rank` (`road_name`, `corner_name`, `max_lean`) + ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='骑遇骑行记录(战报)' + """, + """ + CREATE TABLE IF NOT EXISTS `member_ride_record_like` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `record_id` bigint(20) NOT NULL COMMENT '战报编号 member_ride_record.id', + `user_id` bigint(20) NOT NULL COMMENT '点赞人编号 member_user.id', + `tenant_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '租户编号', + `creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_record_user` (`record_id`, `user_id`) + ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='骑遇骑行战报点赞' + """, +] + +# (user_id, title, road, corner, km, sec, top, curves, max_lean, days_ago, like_user_ids) +MOCK = [ + (131, '南昆山 63 连弯,今天左弯终于不虚了', '南昆山', '观音潭右弯', 87, 5760, 138, 63, 52.0, 1, [133, 132, 134]), + (133, '清晨跑山,观音潭右弯刷出个人最佳', '南昆山', '观音潭右弯', 84, 5400, 132, 58, 49.5, 2, [131, 129]), + (129, '压弯一时爽,一直压弯一直爽', '南昆山', '观音潭右弯', 86, 5900, 129, 61, 46.0, 4, [131]), + (132, '第一次跑南昆山,右弯还是不敢深压', '南昆山', '观音潭右弯', 88, 6300, 112, 57, 33.5, 5, [134]), + (131, '连续 S 弯练车,大腿已废', '南昆山', '连续 S 弯', 52, 3900, 118, 41, 44.5, 1, [133, 129]), + (133, 'S 弯 41 连过,人车合一', '南昆山', '连续 S 弯', 52, 3950, 112, 41, 40.0, 3, [131, 134, 132]), + (129, '大夫山夜骑放风,发卡弯有点意思', '大夫山', '发卡弯', 44, 3660, 105, 38, 43.0, 1, [131, 133]), + (131, '大夫山发卡弯,极速压到 43 度', '大夫山', '发卡弯', 43, 3500, 108, 35, 43.2, 3, [133]), + (134, '傍晚放风,夕阳配弯道,舒服', '大夫山', '发卡弯', 44, 3800, 98, 36, 29.0, 4, [132]), + (131, '罗浮山风门坳长弯,全程 6 档', '罗浮山', '风门坳长弯', 121, 7680, 121, 22, 39.0, 2, [133, 129]), + (133, '风门坳长弯磨膝体验,就一次', '罗浮山', '风门坳长弯', 118, 7500, 126, 20, 47.5, 6, [131]), + (129, '周末摩旅,156 公里赶到营地', '', '', 156, 7680, 121, 22, 25.0, 3, [131, 133, 132, 134]), + (133, '市区通勤顺路兜风,绿道休闲骑', '', '', 32, 3000, 85, 12, 18.5, 1, [131]), + (132, '新手第一份战报,求带飞', '', '', 26, 2700, 76, 9, 15.0, 2, [134]), +] + + +def track_points(seed: int) -> str: + """生成一个小环形轨迹 JSON(约 60 点),让战报详情有图可画。""" + r = 0.004 + (seed % 7) * 0.0004 + pts = [] + n = 60 + for i in range(n): + t = i / n * math.pi * 4 + radius = r + r * 0.35 * math.sin(t * 2.3 + seed) + lat = 23.17 + seed * 0.002 + radius * math.cos(t) + lng = 113.36 + seed * 0.003 + radius * 1.4 * math.sin(t) + speed = round(40 + 60 * (0.5 + 0.5 * math.sin(t * 1.7 + seed)), 1) + pts.append({'lat': round(lat, 6), 'lng': round(lng, 6), 'speed': speed}) + return json.dumps(pts, separators=(',', ':')) + + +def main(): + conn = pymysql.connect(**DB) + cur = conn.cursor() + + for ddl in DDL: + cur.execute(ddl) + conn.commit() + print('DDL ok') + + inserted = 0 + for idx, (uid, title, road, corner, km, sec, top, curves, lean, days_ago, likers) in enumerate(MOCK): + cur.execute("SELECT id FROM member_ride_record WHERE title=%s AND deleted=0", (title,)) + if cur.fetchone(): + print('skip (exists):', title) + continue + cur.execute( + """INSERT INTO member_ride_record + (user_id, title, road_name, corner_name, distance_km, duration_sec, + top_speed_kmh, curve_count, max_lean, track_json, like_count, report_count, + status, tenant_id, creator, create_time) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,0,0,1,%s, + DATE_SUB(NOW(), INTERVAL %s DAY) - INTERVAL %s HOUR)""", + (uid, title, road, corner, km, sec, top, curves, lean, + track_points(idx), len(likers), uid, days_ago, (idx * 3) % 20)) + record_id = cur.lastrowid + for liker in likers: + cur.execute( + """INSERT IGNORE INTO member_ride_record_like + (record_id, user_id, tenant_id, creator, create_time) + VALUES (%s,%s,1,%s, DATE_SUB(NOW(), INTERVAL 1 DAY))""", + (record_id, liker, uid)) + inserted += 1 + print('inserted:', title) + conn.commit() + + cur.execute("SELECT COUNT(*) FROM member_ride_record WHERE deleted=0") + print('total records:', cur.fetchone()[0]) + cur.execute("SELECT road_name, corner_name, COUNT(*) FROM member_ride_record " + "WHERE deleted=0 AND corner_name<>'' GROUP BY road_name, corner_name") + for r in cur.fetchall(): + print('board:', r) + conn.close() + print('done, inserted =', inserted) + + +if __name__ == '__main__': + main() diff --git a/sql/mysql/system/system_menu_subaccount_gender.sql b/sql/mysql/system/system_menu_subaccount_gender.sql new file mode 100644 index 0000000..66d544d --- /dev/null +++ b/sql/mysql/system/system_menu_subaccount_gender.sql @@ -0,0 +1,17 @@ +-- 后台菜单与授权:子账号管理 + 会员管理拆分(男号/女号) +-- 执行库:chat_v1 +-- 1) 新增菜单(幂等:先删后插) +DELETE FROM system_menu WHERE id IN (5810, 5820, 5821); +INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, updater, deleted) +VALUES +(5810, '子账号管理', 'system:sub-account:manage', 2, 20, 0, '/sub-account', 'ep:user', 'system/subaccount/index', 'SystemSubAccount', 0, b'1', b'1', b'1', '1', '1', b'0'), +(5820, '男号管理', 'member:user:query', 2, 1, 2262, 'male-user', 'ep:male', 'member/user/male', 'MemberUserMale', 0, b'1', b'1', b'1', '1', '1', b'0'), +(5821, '女号管理', 'member:user:query', 2, 2, 2262, 'female-user', 'ep:female', 'member/user/female', 'MemberUserFemale', 0, b'1', b'1', b'1', '1', '1', b'0'); + +-- 2) 授权给「APP管理员」角色(AppHolder 当前使用的角色) +DELETE FROM system_role_menu WHERE role_id = 158 AND menu_id IN (5810, 5820, 5821); +INSERT INTO system_role_menu (role_id, menu_id, creator, updater, deleted, tenant_id) +VALUES +(158, 5810, '1', '1', b'0', 1), +(158, 5820, '1', '1', b'0', 1), +(158, 5821, '1', '1', b'0', 1); diff --git a/sql/mysql/system/system_sub_account.sql b/sql/mysql/system/system_sub_account.sql new file mode 100644 index 0000000..3925165 --- /dev/null +++ b/sql/mysql/system/system_sub_account.sql @@ -0,0 +1,17 @@ +-- 子账号管理:holder 与 子账号/专属角色 的绑定关系 +-- 执行位置:chat_v1 库 +CREATE TABLE IF NOT EXISTS `system_sub_account` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `holder_user_id` bigint NOT NULL COMMENT '管理者用户编号(system_users.id)', + `sub_user_id` bigint NOT NULL COMMENT '子账号用户编号(system_users.id)', + `role_id` bigint NOT NULL COMMENT '子账号专属角色编号(system_role.id)', + `creator` varchar(64) NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`), + KEY `idx_holder_user_id` (`holder_user_id`), + KEY `idx_sub_user_id` (`sub_user_id`) +) ENGINE = InnoDB COMMENT '后台子账号表'; diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserPageReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserPageReqVO.java index abb9428..ee5bc15 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserPageReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserPageReqVO.java @@ -41,6 +41,12 @@ public class MemberUserPageReqVO extends PageParam { @Schema(description = "用户分组编号", example = "1") private Long groupId; + @Schema(description = "用户 Code(App 我的页面展示的 ID)", example = "924199") + private String userCode; + + @Schema(description = "性别:1 男 2 女", example = "1") + private Integer sex; + // TODO 芋艿:注册用户类型; // TODO 芋艿:登录用户类型; diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java index e20eb66..c8366c1 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java @@ -92,6 +92,8 @@ public interface MemberUserMapper extends BaseMapperX { .betweenIfPresent(MemberUserDO::getCreateTime, reqVO.getCreateTime()) .eqIfPresent(MemberUserDO::getLevelId, reqVO.getLevelId()) .eqIfPresent(MemberUserDO::getGroupId, reqVO.getGroupId()) + .eqIfPresent(MemberUserDO::getUserCode, reqVO.getUserCode()) + .eqIfPresent(MemberUserDO::getSex, reqVO.getSex()) .apply(StrUtil.isNotEmpty(tagIdSql), tagIdSql) .orderByDesc(MemberUserDO::getId)); } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/recent/RecentServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/recent/RecentServiceImpl.java index 794b4a2..86d96d1 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/recent/RecentServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/recent/RecentServiceImpl.java @@ -63,6 +63,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.member.dal.mysql.recent.RecentMapper; +import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper; import java.math.BigDecimal; import java.math.RoundingMode; @@ -83,6 +84,14 @@ public class RecentServiceImpl implements RecentService { @Resource private RecentMapper recentMapper; @Resource + private MemberUserMapper memberUserMapper; + + /** + * 首页(广场/附近)用户列表不可见账号:这些手机号的用户不会出现在首页用户列表中。 + * 如需增减隐藏账号,改这个列表即可。 + */ + private static final List SQUARE_HIDDEN_MOBILES = Arrays.asList("13311615193", "18800000001"); + @Resource private OperationMapper operationMapper; @Resource @@ -497,6 +506,18 @@ public class RecentServiceImpl implements RecentService { RecentPageReqVO resultQuery = BeanUtil.toBean(bean, RecentPageReqVO.class); resultQuery.setSceneBizType(bean.getScene().getType()); quickBeforeQuery(operationUser, operationCfg, resultQuery); + // 首页不可见账号:按手机号定位,合并进排除列表(广场/附近所有 tab 均不出现) + List excludeIds = resultQuery.getExcludeUserIdList() != null + ? new ArrayList<>(resultQuery.getExcludeUserIdList()) : new ArrayList<>(); + for (String hiddenMobile : SQUARE_HIDDEN_MOBILES) { + MemberUserDO hiddenUser = memberUserMapper.selectByMobile(hiddenMobile); + if (hiddenUser != null && !excludeIds.contains(hiddenUser.getId())) { + excludeIds.add(hiddenUser.getId()); + } + } + if (!excludeIds.isEmpty()) { + resultQuery.setExcludeUserIdList(excludeIds); + } // 广场-最近 if (ObjectUtil.equals(bean.getScene(), MemberRecentSceneEnum.SQUARE_NEW_TIME)) { resultQuery.setDistanceIsNotNull(true); diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayTransferCreateRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayTransferCreateRespDTO.java old mode 100644 new mode 100755 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/WalletTxAuthController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/WalletTxAuthController.java old mode 100644 new mode 100755 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/txauth/WalletTxAuthPageReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/txauth/WalletTxAuthPageReqVO.java old mode 100644 new mode 100755 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/txauth/WalletTxAuthRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/txauth/WalletTxAuthRespVO.java old mode 100644 new mode 100755 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/txauth/WalletTxAuthSaveReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/txauth/WalletTxAuthSaveReqVO.java old mode 100644 new mode 100755 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java old mode 100644 new mode 100755 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/WalletTxAuthDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/WalletTxAuthDO.java old mode 100644 new mode 100755 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/WalletTxAuthMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/WalletTxAuthMapper.java old mode 100644 new mode 100755 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/WalletTxAuthService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/WalletTxAuthService.java old mode 100644 new mode 100755 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/WalletTxAuthServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/WalletTxAuthServiceImpl.java old mode 100644 new mode 100755 diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index bc972b3..c6594c9 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -167,4 +167,10 @@ public interface ErrorCodeConstants { // ========== 站内信发送 1-002-028-000 ========== ErrorCode NOTIFY_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_028_000, "模板参数({})缺失"); + // ========== 子账号管理 1-002-030-000 ========= + ErrorCode SUB_ACCOUNT_NOT_EXISTS = new ErrorCode(1_002_030_000, "子账号不存在"); + ErrorCode SUB_ACCOUNT_NOT_OWNER = new ErrorCode(1_002_030_001, "无权操作该子账号"); + ErrorCode SUB_ACCOUNT_MENU_EMPTY = new ErrorCode(1_002_030_002, "请至少勾选一个当前账号拥有的菜单"); + ErrorCode SUB_ACCOUNT_HOLDER_MENU_EMPTY = new ErrorCode(1_002_030_003, "当前账号未拥有任何菜单,无法创建子账号"); + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/SubAccountController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/SubAccountController.java new file mode 100644 index 0000000..76f39b5 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/SubAccountController.java @@ -0,0 +1,88 @@ +package cn.iocoder.yudao.module.system.controller.admin.subaccount; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.system.controller.admin.subaccount.vo.*; +import cn.iocoder.yudao.module.system.service.subaccount.SubAccountService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +/** + * 后台子账号管理 Controller + * + * 供 AppHolder 管理自己的子账号:创建、分配菜单、禁用、重置密码、删除。 + */ +@Tag(name = "管理后台 - 子账号管理") +@RestController +@RequestMapping("/system/sub-account") +@Validated +public class SubAccountController { + + @Resource + private SubAccountService subAccountService; + + @GetMapping("/list") + @Operation(summary = "获得子账号列表") + @PreAuthorize("@ss.hasPermission('system:sub-account:manage')") + public CommonResult> getSubAccountList() { + return success(subAccountService.getSubAccountList()); + } + + @GetMapping("/menu-list") + @Operation(summary = "获得当前账号拥有的菜单(用于给子账号勾选)") + @PreAuthorize("@ss.hasPermission('system:sub-account:manage')") + public CommonResult> getHolderMenuList() { + return success(subAccountService.getHolderMenuList()); + } + + @PostMapping("/create") + @Operation(summary = "创建子账号") + @PreAuthorize("@ss.hasPermission('system:sub-account:manage')") + public CommonResult createSubAccount(@Valid @RequestBody SubAccountCreateReqVO reqVO) { + return success(subAccountService.createSubAccount(reqVO, reqVO.getPassword())); + } + + @PutMapping("/update") + @Operation(summary = "修改子账号(昵称 + 可见菜单)") + @PreAuthorize("@ss.hasPermission('system:sub-account:manage')") + public CommonResult updateSubAccount(@RequestParam("userId") Long userId, + @Valid @RequestBody SubAccountSaveReqVO reqVO) { + subAccountService.updateSubAccount(userId, reqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "修改子账号状态") + @PreAuthorize("@ss.hasPermission('system:sub-account:manage')") + public CommonResult updateSubAccountStatus(@Valid @RequestBody SubAccountUpdateStatusReqVO reqVO) { + subAccountService.updateSubAccountStatus(reqVO.getUserId(), reqVO.getStatus()); + return success(true); + } + + @PutMapping("/reset-password") + @Operation(summary = "重置子账号密码") + @PreAuthorize("@ss.hasPermission('system:sub-account:manage')") + public CommonResult resetSubAccountPassword(@Valid @RequestBody SubAccountResetPasswordReqVO reqVO) { + subAccountService.resetSubAccountPassword(reqVO.getUserId(), reqVO.getPassword()); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除子账号") + @Parameter(name = "userId", description = "子账号用户编号", required = true) + @PreAuthorize("@ss.hasPermission('system:sub-account:manage')") + public CommonResult deleteSubAccount(@RequestParam("userId") Long userId) { + subAccountService.deleteSubAccount(userId); + return success(true); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountCreateReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountCreateReqVO.java new file mode 100644 index 0000000..d3fac7d --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountCreateReqVO.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.system.controller.admin.subaccount.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.validator.constraints.Length; + +@Schema(description = "管理后台 - 子账号创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class SubAccountCreateReqVO extends SubAccountSaveReqVO { + + @Schema(description = "登录密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountMenuRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountMenuRespVO.java new file mode 100644 index 0000000..823173d --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountMenuRespVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.system.controller.admin.subaccount.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 子账号可用菜单 Response VO") +@Data +public class SubAccountMenuRespVO { + + @Schema(description = "菜单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2262") + private Long id; + + @Schema(description = "父菜单编号", example = "0") + private Long parentId; + + @Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "会员中心") + private String name; + + @Schema(description = "菜单类型:1 目录 2 菜单 3 按钮", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer type; + + @Schema(description = "显示顺序", example = "1") + private Integer sort; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountResetPasswordReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountResetPasswordReqVO.java new file mode 100644 index 0000000..8386bba --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountResetPasswordReqVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.system.controller.admin.subaccount.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +@Schema(description = "管理后台 - 子账号重置密码 Request VO") +@Data +public class SubAccountResetPasswordReqVO { + + @Schema(description = "子账号用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + @NotNull(message = "子账号编号不能为空") + private Long userId; + + @Schema(description = "新密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") + @NotBlank(message = "新密码不能为空") + @Size(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountRespVO.java new file mode 100644 index 0000000..4bb9589 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountRespVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.system.controller.admin.subaccount.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Set; + +@Schema(description = "管理后台 - 子账号 Response VO") +@Data +public class SubAccountRespVO { + + @Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "子账号用户编号(system_users.id)", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + private Long userId; + + @Schema(description = "登录账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "suboperator") + private String username; + + @Schema(description = "昵称", example = "运营小号") + private String nickname; + + @Schema(description = "状态:0 开启 1 禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; + + @Schema(description = "最后登录时间") + private LocalDateTime loginDate; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "可见菜单编号列表") + private Set menuIds; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountSaveReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountSaveReqVO.java new file mode 100644 index 0000000..4d2fd9e --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountSaveReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.system.controller.admin.subaccount.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.Set; + +@Schema(description = "管理后台 - 子账号创建/修改 Request VO") +@Data +public class SubAccountSaveReqVO { + + @Schema(description = "子账号用户名(创建必填,字母+数字 4-30 位)", example = "suboperator") + @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") + private String username; + + @Schema(description = "子账号昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "运营小号") + @NotBlank(message = "子账号昵称不能为空") + @Size(max = 30, message = "子账号昵称长度不能超过30个字符") + private String nickname; + + @Schema(description = "菜单编号列表(必须在当前登录账号拥有的菜单范围内)", example = "[2262, 2317]") + private Set menuIds; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountUpdateStatusReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountUpdateStatusReqVO.java new file mode 100644 index 0000000..0b288a6 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/subaccount/vo/SubAccountUpdateStatusReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.system.controller.admin.subaccount.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 子账号状态修改 Request VO") +@Data +public class SubAccountUpdateStatusReqVO { + + @Schema(description = "子账号用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "200") + @NotNull(message = "子账号编号不能为空") + private Long userId; + + @Schema(description = "状态:0 开启 1 禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/subaccount/SubAccountDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/subaccount/SubAccountDO.java new file mode 100644 index 0000000..8e878cf --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/subaccount/SubAccountDO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.system.dal.dataobject.subaccount; + +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 后台子账号 DO + * + * AppHolder 创建的子账号与专属角色的绑定关系。 + * 子账号登录后台后可见的菜单 = 其专属角色被分配的菜单(由 holder 分配)。 + */ +@TableName("system_sub_account") +@KeySequence("system_sub_account_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class SubAccountDO extends TenantBaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 管理者(holder)用户编号,system_users.id + */ + private Long holderUserId; + /** + * 子账号用户编号,system_users.id + */ + private Long subUserId; + /** + * 子账号专属角色编号,system_role.id + */ + private Long roleId; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/subaccount/SubAccountMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/subaccount/SubAccountMapper.java new file mode 100644 index 0000000..e193183 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/subaccount/SubAccountMapper.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.system.dal.mysql.subaccount; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.system.dal.dataobject.subaccount.SubAccountDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 后台子账号 Mapper + */ +@Mapper +public interface SubAccountMapper extends BaseMapperX { + + default List selectListByHolderUserId(Long holderUserId) { + return selectList(new LambdaQueryWrapperX() + .eq(SubAccountDO::getHolderUserId, holderUserId) + .orderByDesc(SubAccountDO::getId)); + } + + default SubAccountDO selectBySubUserId(Long subUserId) { + return selectOne(SubAccountDO::getSubUserId, subUserId); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/subaccount/SubAccountService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/subaccount/SubAccountService.java new file mode 100644 index 0000000..10717f5 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/subaccount/SubAccountService.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.module.system.service.subaccount; + +import cn.iocoder.yudao.module.system.controller.admin.subaccount.vo.SubAccountMenuRespVO; +import cn.iocoder.yudao.module.system.controller.admin.subaccount.vo.SubAccountRespVO; +import cn.iocoder.yudao.module.system.controller.admin.subaccount.vo.SubAccountSaveReqVO; + +import java.util.List; + +/** + * 后台子账号 Service 接口 + * + * AppHolder 在自己的权限范围内创建子账号,并分配子账号可见的菜单。 + */ +public interface SubAccountService { + + /** + * 获得当前登录账号(holder)的子账号列表 + * + * @return 子账号列表 + */ + List getSubAccountList(); + + /** + * 获得当前登录账号(holder)拥有的菜单(用于给子账号勾选) + * + * @return 菜单列表 + */ + List getHolderMenuList(); + + /** + * 创建子账号(用户名 + 昵称 + 密码 + 可见菜单) + * + * @param reqVO 创建信息(password 需要传) + * @return 子账号用户编号 + */ + Long createSubAccount(SubAccountSaveReqVO reqVO, String password); + + /** + * 修改子账号(昵称 + 可见菜单) + * + * @param userId 子账号用户编号 + * @param reqVO 修改信息 + */ + void updateSubAccount(Long userId, SubAccountSaveReqVO reqVO); + + /** + * 修改子账号状态(禁用后无法登录后台) + * + * @param userId 子账号用户编号 + * @param status 状态 + */ + void updateSubAccountStatus(Long userId, Integer status); + + /** + * 重置子账号密码 + * + * @param userId 子账号用户编号 + * @param password 新密码 + */ + void resetSubAccountPassword(Long userId, String password); + + /** + * 删除子账号(连同专属角色) + * + * @param userId 子账号用户编号 + */ + void deleteSubAccount(Long userId); + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/subaccount/SubAccountServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/subaccount/SubAccountServiceImpl.java new file mode 100644 index 0000000..6a839ff --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/subaccount/SubAccountServiceImpl.java @@ -0,0 +1,230 @@ +package cn.iocoder.yudao.module.system.service.subaccount; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.RandomUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleSaveReqVO; +import cn.iocoder.yudao.module.system.controller.admin.subaccount.vo.SubAccountMenuRespVO; +import cn.iocoder.yudao.module.system.controller.admin.subaccount.vo.SubAccountRespVO; +import cn.iocoder.yudao.module.system.controller.admin.subaccount.vo.SubAccountSaveReqVO; +import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; +import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; +import cn.iocoder.yudao.module.system.dal.dataobject.subaccount.SubAccountDO; +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; +import cn.iocoder.yudao.module.system.dal.mysql.subaccount.SubAccountMapper; +import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum; +import cn.iocoder.yudao.module.system.service.permission.MenuService; +import cn.iocoder.yudao.module.system.service.permission.PermissionService; +import cn.iocoder.yudao.module.system.service.permission.RoleService; +import cn.iocoder.yudao.module.system.service.tenant.TenantService; +import cn.iocoder.yudao.module.system.service.user.AdminUserService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; + +/** + * 后台子账号 Service 实现类 + */ +@Service +@Validated +public class SubAccountServiceImpl implements SubAccountService { + + @Resource + private SubAccountMapper subAccountMapper; + @Resource + private AdminUserService userService; + @Resource + private RoleService roleService; + @Resource + private PermissionService permissionService; + @Resource + private MenuService menuService; + @Resource + private TenantService tenantService; + + @Override + public List getSubAccountList() { + Long holderUserId = getLoginUserId(); + List mappings = subAccountMapper.selectListByHolderUserId(holderUserId); + List result = new ArrayList<>(); + for (SubAccountDO mapping : mappings) { + AdminUserDO user = userService.getUser(mapping.getSubUserId()); + if (user == null) { // 用户已被删除,跳过 + continue; + } + SubAccountRespVO vo = BeanUtils.toBean(mapping, SubAccountRespVO.class); + vo.setUserId(user.getId()); + vo.setUsername(user.getUsername()); + vo.setNickname(user.getNickname()); + vo.setStatus(user.getStatus()); + vo.setLoginDate(user.getLoginDate()); + vo.setMenuIds(permissionService.getRoleMenuListByRoleId(mapping.getRoleId())); + result.add(vo); + } + return result; + } + + @Override + public List getHolderMenuList() { + Set holderMenuIds = getHolderMenuIds(getLoginUserId()); + if (CollUtil.isEmpty(holderMenuIds)) { + return new ArrayList<>(); + } + List menus = menuService.getMenuList(holderMenuIds); + menus = menuService.filterDisableMenus(menus); + List result = new ArrayList<>(); + for (MenuDO menu : menus) { + SubAccountMenuRespVO vo = new SubAccountMenuRespVO(); + vo.setId(menu.getId()); + vo.setParentId(menu.getParentId()); + vo.setName(menu.getName()); + vo.setType(menu.getType()); + vo.setSort(menu.getSort()); + result.add(vo); + } + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createSubAccount(SubAccountSaveReqVO reqVO, String password) { + Long holderUserId = getLoginUserId(); + // 1. 校验账号唯一 + if (userService.getUserByUsername(reqVO.getUsername()) != null) { + throw exception(USER_USERNAME_EXISTS); + } + // 2. 校验并过滤菜单:只能授予 holder 自己拥有的菜单 + Set menuIds = filterMenusByHolder(reqVO.getMenuIds(), holderUserId); + // 3. 创建子账号用户 + UserSaveReqVO userReqVO = new UserSaveReqVO(); + userReqVO.setUsername(reqVO.getUsername()); + userReqVO.setNickname(reqVO.getNickname()); + userReqVO.setPassword(password); + userReqVO.setRemark("AppHolder 子账号"); + Long subUserId = userService.createUser(userReqVO); + // 4. 创建专属角色并分配菜单 + Long roleId = createSubRole(subUserId, reqVO.getNickname()); + permissionService.assignRoleMenu(roleId, menuIds); + // 5. 绑定角色 + permissionService.assignUserRole(subUserId, CollUtil.newHashSet(roleId)); + // 6. 记录绑定关系 + SubAccountDO mapping = new SubAccountDO(); + mapping.setHolderUserId(holderUserId); + mapping.setSubUserId(subUserId); + mapping.setRoleId(roleId); + subAccountMapper.insert(mapping); + return subUserId; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSubAccount(Long userId, SubAccountSaveReqVO reqVO) { + SubAccountDO mapping = validateOwner(userId); + // 1. 更新昵称(username 不允许修改,密码走独立接口) + AdminUserDO user = userService.getUser(userId); + UserSaveReqVO userReqVO = new UserSaveReqVO(); + userReqVO.setId(userId); + userReqVO.setUsername(user.getUsername()); + userReqVO.setNickname(reqVO.getNickname()); + userService.updateUser(userReqVO); + // 2. 更新可见菜单 + Set menuIds = filterMenusByHolder(reqVO.getMenuIds(), mapping.getHolderUserId()); + permissionService.assignRoleMenu(mapping.getRoleId(), menuIds); + } + + @Override + public void updateSubAccountStatus(Long userId, Integer status) { + validateOwner(userId); + userService.updateUserStatus(userId, status); + } + + @Override + public void resetSubAccountPassword(Long userId, String password) { + validateOwner(userId); + userService.updateUserPassword(userId, password); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteSubAccount(Long userId) { + SubAccountDO mapping = validateOwner(userId); + // 1. 删除用户 + userService.deleteUser(userId); + // 2. 删除专属角色 + roleService.deleteRole(mapping.getRoleId()); + // 3. 删除绑定关系 + subAccountMapper.deleteById(mapping.getId()); + } + + private Long createSubRole(Long subUserId, String nickname) { + RoleSaveReqVO roleReqVO = new RoleSaveReqVO(); + roleReqVO.setName("子账号-" + nickname); + roleReqVO.setCode("sub_acc_" + subUserId + "_" + RandomUtil.randomString(6).toLowerCase()); + roleReqVO.setSort(999); + roleReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + roleReqVO.setRemark("AppHolder 子账号专属角色"); + return roleService.createRole(roleReqVO, RoleTypeEnum.CUSTOM.getType()); + } + + /** + * 校验当前登录人是否为该子账号的管理者,并返回绑定关系 + */ + private SubAccountDO validateOwner(Long subUserId) { + SubAccountDO mapping = subAccountMapper.selectBySubUserId(subUserId); + if (mapping == null) { + throw exception(SUB_ACCOUNT_NOT_EXISTS); + } + if (!mapping.getHolderUserId().equals(getLoginUserId())) { + throw exception(SUB_ACCOUNT_NOT_OWNER); + } + return mapping; + } + + /** + * 获得 holder 拥有的菜单编号集合(其所有角色菜单的并集) + */ + private Set getHolderMenuIds(Long holderUserId) { + Set roleIds = permissionService.getUserRoleIdListByUserId(holderUserId); + if (CollUtil.isEmpty(roleIds)) { + return new HashSet<>(); + } + return permissionService.getRoleMenuListByRoleId(roleIds); + } + + /** + * 将请求的菜单过滤为 holder 自己拥有的菜单;若过滤后为空则报错 + */ + private Set filterMenusByHolder(Set requestMenuIds, Long holderUserId) { + Set holderMenuIds = getHolderMenuIds(holderUserId); + if (CollUtil.isEmpty(holderMenuIds)) { + throw exception(SUB_ACCOUNT_HOLDER_MENU_EMPTY); + } + Set menuIds = new HashSet<>(); + if (CollUtil.isNotEmpty(requestMenuIds)) { + tenantService.handleTenantMenu(menuIds0 -> { + for (Long menuId : requestMenuIds) { + if (holderMenuIds.contains(menuId) && menuIds0.contains(menuId)) { + menuIds.add(menuId); + } + } + }); + } + if (CollUtil.isEmpty(menuIds)) { + throw exception(SUB_ACCOUNT_MENU_EMPTY); + } + return menuIds; + } + +} diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index 65ae08c..3513e00 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -50,12 +50,12 @@ spring: master: url: jdbc:mysql://139.224.25.207:3306/chat_v1?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 username: chat_v1 - password: BpJ2Ebt4zMPjBcCL + password: rfBzbFFdW4kepybz slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 lazy: true # 开启懒加载,保证启动速度 url: jdbc:mysql://139.224.25.207:3306/chat_v1?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 username: chat_v1 - password: BpJ2Ebt4zMPjBcCL + password: rfBzbFFdW4kepybz # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 redis: diff --git a/yudao-server/src/main/resources/application-prod.yaml b/yudao-server/src/main/resources/application-prod.yaml index 35a15d1..840d3fe 100644 --- a/yudao-server/src/main/resources/application-prod.yaml +++ b/yudao-server/src/main/resources/application-prod.yaml @@ -47,12 +47,12 @@ spring: master: url: jdbc:mysql://127.0.0.1:3306/chat_v1?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true username: chat_v1 - password: BpJ2Ebt4zMPjBcCL + password: rfBzbFFdW4kepybz slave: # 模拟从库,可根据自己需要修改 lazy: true # 开启懒加载,保证启动速度 url: jdbc:mysql://192.168.0.7:3306/chat_v1?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true username: chat_v1 - password: BpJ2Ebt4zMPjBcCL + password: rfBzbFFdW4kepybz # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 redis: host: 127.0.0.1 # 地址