清除多租户功能

This commit is contained in:
2026-02-04 15:41:21 +08:00
parent a2203e4f40
commit 3f65a55c15
189 changed files with 94 additions and 8587 deletions

View File

@@ -37,10 +37,6 @@
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-biz-ip</artifactId>

View File

@@ -3,7 +3,6 @@ package com.njcn.msgpush.module.system.api.oauth2;
import com.njcn.msgpush.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.njcn.msgpush.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import com.njcn.msgpush.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
import com.njcn.msgpush.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenRespDTO;
@@ -30,7 +29,6 @@ public class OAuth2TokenApiImpl implements OAuth2TokenCommonApi {
}
@Override
@TenantIgnore // 访问令牌校验时,无需传递租户编号;主要解决上传文件的场景,前端不会传递 tenant-id
public CommonResult<OAuth2AccessTokenCheckRespDTO> checkAccessToken(String accessToken) {
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.checkAccessToken(accessToken);
return success(BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenCheckRespDTO.class));

View File

@@ -1,35 +0,0 @@
package com.njcn.msgpush.module.system.api.tenant;
import com.njcn.msgpush.framework.common.biz.system.tenant.TenantCommonApi;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.njcn.msgpush.module.system.service.tenant.TenantService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import jakarta.annotation.Resource;
import java.util.List;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口,给 Feign 调用
@Validated
public class TenantApiImpl implements TenantCommonApi {
@Resource
private TenantService tenantService;
@Override
@TenantIgnore // 防止递归。避免调用 /rpc-api/system/tenant/valid 接口时,又去触发 /rpc-api/system/tenant/valid 去校验
public CommonResult<List<Long>> getTenantIdList() {
return success(tenantService.getTenantIdList());
}
@Override
@TenantIgnore // 获得租户列表的时候,无需传递租户编号
public CommonResult<Boolean> validTenant(Long id) {
tenantService.validTenant(id);
return success(true);
}
}

View File

@@ -1,7 +1,7 @@
### 请求 /login 接口 => 成功
POST {{baseUrl}}/system/auth/login
Content-Type: application/json
tenant-id: {{adminTenantId}}
tag: Yunai.local
{
@@ -14,7 +14,7 @@ tag: Yunai.local
### 请求 /login 接口【加密 AES】 => 成功
POST {{baseUrl}}/system/auth/login
Content-Type: application/json
tenant-id: {{adminTenantId}}
tag: Yunai.local
X-API-ENCRYPT: true
@@ -23,7 +23,7 @@ WvSX9MOrenyGfBhEM0g1/hHgq8ocktMZ9OwAJ6MOG5FUrzYF/rG5JF1eMptQM1wT73VgDS05l/37WeRt
### 请求 /login 接口【加密 RSA】 => 成功
POST {{baseUrl}}/system/auth/login
Content-Type: application/json
tenant-id: {{adminTenantId}}
tag: Yunai.local
X-API-ENCRYPT: true
@@ -32,7 +32,7 @@ e7QZTork9ZV5CmgZvSd+cHZk3xdUxKtowLM02kOha+gxHK2H/daU8nVBYS3+bwuDRy5abf+Pz1QJJGVA
### 请求 /login 接口 => 成功(无验证码)
POST {{baseUrl}}/system/auth/login
Content-Type: application/json
tenant-id: {{adminTenantId}}
{
"username": "admin",
@@ -42,10 +42,10 @@ tenant-id: {{adminTenantId}}
### 请求 /get-permission-info 接口 => 成功
GET {{baseUrl}}/system/auth/get-permission-info
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
### 请求 /list-menus 接口 => 成功
GET {{baseUrl}}/system/list-menus
Authorization: Bearer {{token}}
#Authorization: Bearer a6aa7714a2e44c95aaa8a2c5adc2a67a
tenant-id: {{adminTenantId}}

View File

@@ -2,7 +2,7 @@ package com.njcn.msgpush.module.system.controller.admin.captcha;
import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
@@ -27,7 +27,6 @@ public class CaptchaController {
@PostMapping({"/get"})
@Operation(summary = "获得验证码")
@PermitAll
@TenantIgnore
public ResponseModel get(@RequestBody CaptchaVO data, HttpServletRequest request) {
assert request.getRemoteHost() != null;
data.setBrowserInfo(getRemoteId(request));
@@ -37,7 +36,6 @@ public class CaptchaController {
@PostMapping("/check")
@Operation(summary = "校验验证码")
@PermitAll
@TenantIgnore
public ResponseModel check(@RequestBody CaptchaVO data, HttpServletRequest request) {
data.setBrowserInfo(getRemoteId(request));
return captchaService.check(data);

View File

@@ -1,4 +1,4 @@
### 请求 /menu/list 接口 => 成功
GET {{baseUrl}}/system/dict-data/list-all-simple
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}

View File

@@ -1,5 +1,5 @@
### 获得地区树
GET {{baseUrl}}/system/area/tree
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}

View File

@@ -1,4 +1,4 @@
### 请求 /system/operate-log/page 接口 => 成功
GET {{baseUrl}}/system/operate-log/page
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}

View File

@@ -2,7 +2,7 @@
POST {{baseUrl}}/system/oauth2-client/create
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
{
"id": "1",

View File

@@ -1,13 +1,13 @@
### 请求 /system/oauth2/authorize 接口 => 成功
GET {{baseUrl}}/system/oauth2/authorize?clientId=default
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
### 请求 /system/oauth2/authorize + token 接口 => 成功
POST {{baseUrl}}/system/oauth2/authorize
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
response_type=token&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=true
@@ -15,7 +15,7 @@ response_type=token&client_id=default&scope={"user.read": true}&redirect_uri=htt
POST {{baseUrl}}/system/oauth2/authorize
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
response_type=code&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=false
@@ -23,7 +23,7 @@ response_type=code&client_id=default&scope={"user.read": true}&redirect_uri=http
POST {{baseUrl}}/system/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
tenant-id: {{adminTenantId}}
grant_type=authorization_code&redirect_uri=https://www.iocoder.cn&code=189956c07a174588a97157eabef2f93a
@@ -31,7 +31,7 @@ grant_type=authorization_code&redirect_uri=https://www.iocoder.cn&code=189956c07
POST {{baseUrl}}/system/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
tenant-id: {{adminTenantId}}
grant_type=password&username=admin&password=admin123&scope=user.read
@@ -39,7 +39,7 @@ grant_type=password&username=admin&password=admin123&scope=user.read
POST {{baseUrl}}/system/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
tenant-id: {{adminTenantId}}
grant_type=client_credentials&scope=user.read
@@ -47,16 +47,16 @@ grant_type=client_credentials&scope=user.read
POST {{baseUrl}}/system/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
tenant-id: {{adminTenantId}}
grant_type=refresh_token&refresh_token=00895465d6994f72a9d926ceeed0f588
### 请求 /system/oauth2/token + DELETE 接口 => 成功
DELETE {{baseUrl}}/system/oauth2/token?token=ca8a188f464441d6949c51493a2b7596
Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
tenant-id: {{adminTenantId}}
### 请求 /system/oauth2/check-token 接口 => 成功
POST {{baseUrl}}/system/oauth2/check-token?token=620d307c5b4148df8a98dd6c6c547106
Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
tenant-id: {{adminTenantId}}

View File

@@ -1,13 +1,13 @@
### 请求 /system/oauth2/user/get 接口 => 成功
GET {{baseUrl}}/system/oauth2/user/get
Authorization: Bearer 47f9c74ec11041f193b777ebb95c3b0d
tenant-id: {{adminTenantId}}
### 请求 /system/oauth2/user/update 接口 => 成功
PUT {{baseUrl}}/system/oauth2/user/update
Content-Type: application/json
Authorization: Bearer 47f9c74ec11041f193b777ebb95c3b0d
tenant-id: {{adminTenantId}}
{
"nickname": "灿能源码"

View File

@@ -20,9 +20,6 @@ public class OAuth2OpenCheckTokenRespVO {
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@JsonProperty("user_type")
private Integer userType;
@Schema(description = "租户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@JsonProperty("tenant_id")
private Long tenantId;
@Schema(description = "客户端编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "car")
@JsonProperty("client_id")

View File

@@ -1,4 +1,4 @@
### 请求 /menu/list 接口 => 成功
GET {{baseUrl}}/system/menu/list
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}

View File

@@ -75,17 +75,6 @@ public class MenuController {
return success(BeanUtils.toBean(list, MenuRespVO.class));
}
@GetMapping({"/list-all-simple", "simple-list"})
@Operation(summary = "获取菜单精简信息列表",
description = "只包含被开启的菜单,用于【角色分配菜单】功能的选项。在多租户的场景下,会只返回租户所在套餐有的菜单")
public CommonResult<List<MenuSimpleRespVO>> getSimpleMenuList() {
List<MenuDO> list = menuService.getMenuListByTenant(
new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()));
list = menuService.filterDisableMenus(list);
list.sort(Comparator.comparing(MenuDO::getSort));
return success(BeanUtils.toBean(list, MenuSimpleRespVO.class));
}
@GetMapping("/get")
@Operation(summary = "获取菜单信息")
@PreAuthorize("@ss.hasPermission('system:menu:query')")

View File

@@ -6,7 +6,6 @@ import com.njcn.msgpush.module.system.controller.admin.permission.vo.permission.
import com.njcn.msgpush.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleMenuReqVO;
import com.njcn.msgpush.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
import com.njcn.msgpush.module.system.service.permission.PermissionService;
import com.njcn.msgpush.module.system.service.tenant.TenantService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -32,8 +31,6 @@ public class PermissionController {
@Resource
private PermissionService permissionService;
@Resource
private TenantService tenantService;
@Operation(summary = "获得角色拥有的菜单编号")
@Parameter(name = "roleId", description = "角色编号", required = true)
@@ -47,9 +44,6 @@ public class PermissionController {
@Operation(summary = "赋予角色菜单")
@PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
public CommonResult<Boolean> assignRoleMenu(@Validated @RequestBody PermissionAssignRoleMenuReqVO reqVO) {
// 开启多租户的情况下,需要过滤掉未开通的菜单
tenantService.handleTenantMenu(menuIds -> reqVO.getMenuIds().removeIf(menuId -> !CollUtil.contains(menuIds, menuId)));
// 执行菜单的分配
permissionService.assignRoleMenu(reqVO.getRoleId(), reqVO.getMenuIds());
return success(true);

View File

@@ -2,7 +2,7 @@
POST {{baseUrl}}/system/role/create
Authorization: Bearer {{token}}
Content-Type: application/json
tenant-id: {{adminTenantId}}
{
"name": "测试角色",
@@ -14,7 +14,7 @@ tenant-id: {{adminTenantId}}
POST {{baseUrl}}/system/role/update
Authorization: Bearer {{token}}
Content-Type: application/json
tenant-id: {{adminTenantId}}
{
"id": 100,
@@ -26,7 +26,7 @@ tenant-id: {{adminTenantId}}
POST {{baseUrl}}/system/role/delete
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
roleId=14
@@ -34,9 +34,9 @@ roleId=14
GET {{baseUrl}}/system/role/get?id=100
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
### /role/page 成功
GET {{baseUrl}}/system/role/page?pageNo=1&pageSize=10
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}

View File

@@ -1,21 +0,0 @@
### 获取租户编号 /admin-api/system/get-id-by-name
GET {{baseUrl}}/system/tenant/get-id-by-name?name=灿能源码
### 创建租户 /admin-api/system/tenant/create
POST {{baseUrl}}/system/tenant/create
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
{
"name": "灿能",
"contactName": "芋艿",
"contactMobile": "15601691300",
"status": 0,
"domain": "https://www.iocoder.cn",
"packageId": 110,
"expireTime": 1699545600000,
"accountCount": 20,
"username": "admin",
"password": "123321"
}

View File

@@ -1,136 +0,0 @@
package com.njcn.msgpush.module.system.controller.admin.tenant;
import com.njcn.msgpush.framework.apilog.core.annotation.ApiAccessLog;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.pojo.PageParam;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.framework.excel.core.util.ExcelUtils;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant.TenantRespVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantDO;
import com.njcn.msgpush.module.system.service.tenant.TenantService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static com.njcn.msgpush.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
import static com.njcn.msgpush.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - 租户")
@RestController
@RequestMapping("/system/tenant")
public class TenantController {
@Resource
private TenantService tenantService;
@GetMapping("/get-id-by-name")
@PermitAll
@TenantIgnore
@Operation(summary = "使用租户名,获得租户编号", description = "登录界面,根据用户的租户名,获得租户编号")
@Parameter(name = "name", description = "租户名", required = true, example = "1024")
public CommonResult<Long> getTenantIdByName(@RequestParam("name") String name) {
TenantDO tenant = tenantService.getTenantByName(name);
return success(tenant != null ? tenant.getId() : null);
}
@GetMapping({ "simple-list" })
@PermitAll
@TenantIgnore
@Operation(summary = "获取租户精简信息列表", description = "只包含被开启的租户,用于【首页】功能的选择租户选项")
public CommonResult<List<TenantRespVO>> getTenantSimpleList() {
List<TenantDO> list = tenantService.getTenantListByStatus(CommonStatusEnum.ENABLE.getStatus());
return success(convertList(list, tenantDO ->
new TenantRespVO().setId(tenantDO.getId()).setName(tenantDO.getName())));
}
@GetMapping("/get-by-website")
@PermitAll
@TenantIgnore
@Operation(summary = "使用域名,获得租户信息", description = "登录界面,根据用户的域名,获得租户信息")
@Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn")
public CommonResult<TenantRespVO> getTenantByWebsite(@RequestParam("website") String website) {
TenantDO tenant = tenantService.getTenantByWebsite(website);
if (tenant == null || CommonStatusEnum.isDisable(tenant.getStatus())) {
return success(null);
}
return success(new TenantRespVO().setId(tenant.getId()).setName(tenant.getName()));
}
@PostMapping("/create")
@Operation(summary = "创建租户")
@PreAuthorize("@ss.hasPermission('system:tenant:create')")
public CommonResult<Long> createTenant(@Valid @RequestBody TenantSaveReqVO createReqVO) {
return success(tenantService.createTenant(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新租户")
@PreAuthorize("@ss.hasPermission('system:tenant:update')")
public CommonResult<Boolean> updateTenant(@Valid @RequestBody TenantSaveReqVO updateReqVO) {
tenantService.updateTenant(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除租户")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:tenant:delete')")
public CommonResult<Boolean> deleteTenant(@RequestParam("id") Long id) {
tenantService.deleteTenant(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Parameter(name = "ids", description = "编号列表", required = true)
@Operation(summary = "批量删除租户")
@PreAuthorize("@ss.hasPermission('system:tenant:delete')")
public CommonResult<Boolean> deleteTenantList(@RequestParam("ids") List<Long> ids) {
tenantService.deleteTenantList(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得租户")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:tenant:query')")
public CommonResult<TenantRespVO> getTenant(@RequestParam("id") Long id) {
TenantDO tenant = tenantService.getTenant(id);
return success(BeanUtils.toBean(tenant, TenantRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得租户分页")
@PreAuthorize("@ss.hasPermission('system:tenant:query')")
public CommonResult<PageResult<TenantRespVO>> getTenantPage(@Valid TenantPageReqVO pageVO) {
PageResult<TenantDO> pageResult = tenantService.getTenantPage(pageVO);
return success(BeanUtils.toBean(pageResult, TenantRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出租户 Excel")
@PreAuthorize("@ss.hasPermission('system:tenant:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportTenantExcel(@Valid TenantPageReqVO exportReqVO, HttpServletResponse response) throws IOException {
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<TenantDO> list = tenantService.getTenantPage(exportReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "租户.xls", "数据", TenantRespVO.class,
BeanUtils.toBean(list, TenantRespVO.class));
}
}

View File

@@ -1,92 +0,0 @@
package com.njcn.msgpush.module.system.controller.admin.tenant;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages.TenantPackageRespVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages.TenantPackageSimpleRespVO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantPackageDO;
import com.njcn.msgpush.module.system.service.tenant.TenantPackageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 租户套餐")
@RestController
@RequestMapping("/system/tenant-package")
@Validated
public class TenantPackageController {
@Resource
private TenantPackageService tenantPackageService;
@PostMapping("/create")
@Operation(summary = "创建租户套餐")
@PreAuthorize("@ss.hasPermission('system:tenant-package:create')")
public CommonResult<Long> createTenantPackage(@Valid @RequestBody TenantPackageSaveReqVO createReqVO) {
return success(tenantPackageService.createTenantPackage(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新租户套餐")
@PreAuthorize("@ss.hasPermission('system:tenant-package:update')")
public CommonResult<Boolean> updateTenantPackage(@Valid @RequestBody TenantPackageSaveReqVO updateReqVO) {
tenantPackageService.updateTenantPackage(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除租户套餐")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('system:tenant-package:delete')")
public CommonResult<Boolean> deleteTenantPackage(@RequestParam("id") Long id) {
tenantPackageService.deleteTenantPackage(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Parameter(name = "ids", description = "编号列表", required = true)
@Operation(summary = "批量删除租户套餐")
@PreAuthorize("@ss.hasPermission('system:tenant-package:delete')")
public CommonResult<Boolean> deleteTenantPackageList(@RequestParam("ids") List<Long> ids) {
tenantPackageService.deleteTenantPackageList(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得租户套餐")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:tenant-package:query')")
public CommonResult<TenantPackageRespVO> getTenantPackage(@RequestParam("id") Long id) {
TenantPackageDO tenantPackage = tenantPackageService.getTenantPackage(id);
return success(BeanUtils.toBean(tenantPackage, TenantPackageRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得租户套餐分页")
@PreAuthorize("@ss.hasPermission('system:tenant-package:query')")
public CommonResult<PageResult<TenantPackageRespVO>> getTenantPackagePage(@Valid TenantPackagePageReqVO pageVO) {
PageResult<TenantPackageDO> pageResult = tenantPackageService.getTenantPackagePage(pageVO);
return success(BeanUtils.toBean(pageResult, TenantPackageRespVO.class));
}
@GetMapping({"/get-simple-list", "simple-list"})
@Operation(summary = "获取租户套餐精简信息列表", description = "只包含被开启的租户套餐,主要用于前端的下拉选项")
public CommonResult<List<TenantPackageSimpleRespVO>> getTenantPackageList() {
List<TenantPackageDO> list = tenantPackageService.getTenantPackageListByStatus(CommonStatusEnum.ENABLE.getStatus());
return success(BeanUtils.toBean(list, TenantPackageSimpleRespVO.class));
}
}

View File

@@ -1,32 +0,0 @@
package com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages;
import com.njcn.msgpush.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 租户套餐分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TenantPackagePageReqVO extends PageParam {
@Schema(description = "套餐名", example = "VIP")
private String name;
@Schema(description = "状态", example = "1")
private Integer status;
@Schema(description = "备注", example = "")
private String remark;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@@ -1,31 +0,0 @@
package com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages;
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 TenantPackageRespVO {
@Schema(description = "套餐编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "VIP")
private String name;
@Schema(description = "状态,参见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "备注", example = "")
private String remark;
@Schema(description = "关联的菜单编号", requiredMode = Schema.RequiredMode.REQUIRED)
private Set<Long> menuIds;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -1,35 +0,0 @@
package com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.Set;
@Schema(description = "管理后台 - 租户套餐创建/修改 Request VO")
@Data
public class TenantPackageSaveReqVO {
@Schema(description = "套餐编号", example = "1024")
private Long id;
@Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "VIP")
@NotEmpty(message = "套餐名不能为空")
private String name;
@Schema(description = "状态,参见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态不能为空")
@InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}")
private Integer status;
@Schema(description = "备注", example = "")
private String remark;
@Schema(description = "关联的菜单编号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "关联的菜单编号不能为空")
private Set<Long> menuIds;
}

View File

@@ -1,20 +0,0 @@
package com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
@Schema(description = "管理后台 - 租户套餐精简 Response VO")
@Data
public class TenantPackageSimpleRespVO {
@Schema(description = "套餐编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "套餐编号不能为空")
private Long id;
@Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "VIP")
@NotNull(message = "套餐名不能为空")
private String name;
}

View File

@@ -1,36 +0,0 @@
package com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant;
import com.njcn.msgpush.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 租户分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TenantPageReqVO extends PageParam {
@Schema(description = "租户名", example = "灿能")
private String name;
@Schema(description = "联系人", example = "芋艿")
private String contactName;
@Schema(description = "联系手机", example = "15601691300")
private String contactMobile;
@Schema(description = "租户状态0正常 1停用", example = "1")
private Integer status;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@@ -1,56 +0,0 @@
package com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import com.njcn.msgpush.framework.excel.core.annotations.DictFormat;
import com.njcn.msgpush.framework.excel.core.convert.DictConvert;
import com.njcn.msgpush.module.system.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 租户 Response VO")
@Data
@ExcelIgnoreUnannotated
public class TenantRespVO {
@Schema(description = "租户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("租户编号")
private Long id;
@Schema(description = "租户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "灿能")
@ExcelProperty("租户名")
private String name;
@Schema(description = "联系人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@ExcelProperty("联系人")
private String contactName;
@Schema(description = "联系手机", example = "15601691300")
@ExcelProperty("联系手机")
private String contactMobile;
@Schema(description = "租户状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.COMMON_STATUS)
private Integer status;
@Schema(description = "绑定域名数组", example = "https://www.iocoder.cn")
private List<String> websites;
@Schema(description = "租户套餐编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long packageId;
@Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime expireTime;
@Schema(description = "账号数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer accountCount;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@@ -1,71 +0,0 @@
package com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 租户创建/修改 Request VO")
@Data
public class TenantSaveReqVO {
@Schema(description = "租户编号", example = "1024")
private Long id;
@Schema(description = "租户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "灿能")
@NotNull(message = "租户名不能为空")
private String name;
@Schema(description = "联系人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@NotNull(message = "联系人不能为空")
private String contactName;
@Schema(description = "联系手机", example = "15601691300")
private String contactMobile;
@Schema(description = "租户状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "租户状态")
private Integer status;
@Schema(description = "绑定域名数组", example = "https://www.iocoder.cn")
private List<String> websites;
@Schema(description = "租户套餐编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "租户套餐编号不能为空")
private Long packageId;
@Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "过期时间不能为空")
private LocalDateTime expireTime;
@Schema(description = "账号数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "账号数量不能为空")
private Integer accountCount;
// ========== 仅【创建】时,需要传递的字段 ==========
@Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "msgpush")
@Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成")
@Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符")
private String username;
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
@AssertTrue(message = "用户账号、密码不能为空")
@JsonIgnore
public boolean isUsernameValid() {
return id != null // 修改时,不需要传递
|| (ObjectUtil.isAllNotEmpty(username, password)); // 新增时,必须都传递 username、password
}
}

View File

@@ -2,10 +2,9 @@
GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10
Authorization: Bearer {{token}}
#Authorization: Bearer test100
tenant-id: {{adminTenantId}}
### 请求 /system/user/page 接口(测试访问别的租户)
GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
visit-tenant-id: 122

View File

@@ -1,4 +1,4 @@
### 请求 /system/user/profile/get 接口 => 没有权限
GET {{baseUrl}}/system/user/profile/get
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}

View File

@@ -1,43 +0,0 @@
package com.njcn.msgpush.module.system.controller.app.tenant;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.njcn.msgpush.module.system.controller.app.tenant.vo.AppTenantRespVO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantDO;
import com.njcn.msgpush.module.system.service.tenant.TenantService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@Tag(name = "用户 App - 租户")
@RestController
@RequestMapping("/system/tenant")
public class AppTenantController {
@Resource
private TenantService tenantService;
@GetMapping("/get-by-website")
@PermitAll
@TenantIgnore
@Operation(summary = "使用域名,获得租户信息", description = "根据用户的域名,获得租户信息")
@Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn")
public CommonResult<AppTenantRespVO> getTenantByWebsite(@RequestParam("website") String website) {
TenantDO tenant = tenantService.getTenantByWebsite(website);
if (tenant == null || CommonStatusEnum.isDisable(tenant.getStatus())) {
return success(null);
}
return success(BeanUtils.toBean(tenant, AppTenantRespVO.class));
}
}

View File

@@ -1,16 +0,0 @@
package com.njcn.msgpush.module.system.controller.app.tenant.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户 App - 租户 Response VO")
@Data
public class AppTenantRespVO {
@Schema(description = "租户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "租户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "灿能")
private String name;
}

View File

@@ -1,26 +0,0 @@
package com.njcn.msgpush.module.system.convert.tenant;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;
import com.njcn.msgpush.module.system.controller.admin.user.vo.user.UserSaveReqVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 租户 Convert
*
* @author hongawen
*/
@Mapper
public interface TenantConvert {
TenantConvert INSTANCE = Mappers.getMapper(TenantConvert.class);
default UserSaveReqVO convert02(TenantSaveReqVO bean) {
UserSaveReqVO reqVO = new UserSaveReqVO();
reqVO.setUsername(bean.getUsername());
reqVO.setPassword(bean.getPassword());
reqVO.setNickname(bean.getContactName()).setMobile(bean.getContactMobile());
return reqVO;
}
}

View File

@@ -1,7 +1,7 @@
package com.njcn.msgpush.module.system.dal.dataobject.dept;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.tenant.core.db.TenantBaseDO;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.module.system.dal.dataobject.user.AdminUserDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,7 +19,7 @@ import lombok.EqualsAndHashCode;
@KeySequence("system_dept_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class DeptDO extends TenantBaseDO {
public class DeptDO extends BaseDO {
public static final Long PARENT_ID_ROOT = 0L;

View File

@@ -2,7 +2,6 @@ package com.njcn.msgpush.module.system.dal.dataobject.dict;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -16,7 +15,6 @@ import lombok.EqualsAndHashCode;
@KeySequence("system_dict_data_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@TenantIgnore
public class DictDataDO extends BaseDO {
/**

View File

@@ -2,7 +2,6 @@ package com.njcn.msgpush.module.system.dal.dataobject.dict;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -23,7 +22,6 @@ import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TenantIgnore
public class DictTypeDO extends BaseDO {
/**

View File

@@ -1,7 +1,7 @@
package com.njcn.msgpush.module.system.dal.dataobject.mail;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -20,7 +20,6 @@ import lombok.EqualsAndHashCode;
@KeySequence("system_mail_account_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@TenantIgnore
public class MailAccountDO extends BaseDO {
/**

View File

@@ -3,7 +3,7 @@ package com.njcn.msgpush.module.system.dal.dataobject.mail;
import com.njcn.msgpush.framework.common.enums.UserTypeEnum;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.framework.mybatis.core.type.StringListTypeHandler;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.njcn.msgpush.module.system.enums.mail.MailSendStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
@@ -31,7 +31,6 @@ import java.util.Map;
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TenantIgnore
public class MailLogDO extends BaseDO implements Serializable {
/**

View File

@@ -2,7 +2,7 @@ package com.njcn.msgpush.module.system.dal.dataobject.mail;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -22,7 +22,6 @@ import java.util.List;
@KeySequence("system_mail_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@TenantIgnore
public class MailTemplateDO extends BaseDO {
/**

View File

@@ -2,7 +2,7 @@ package com.njcn.msgpush.module.system.dal.dataobject.notify;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -25,7 +25,6 @@ import java.util.List;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TenantIgnore
public class NotifyTemplateDO extends BaseDO {
/**

View File

@@ -1,12 +1,12 @@
package com.njcn.msgpush.module.system.dal.dataobject.oauth2;
import com.njcn.msgpush.framework.common.enums.UserTypeEnum;
import com.njcn.msgpush.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -26,7 +26,7 @@ import java.util.Map;
@KeySequence("system_oauth2_access_token_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class OAuth2AccessTokenDO extends TenantBaseDO {
public class OAuth2AccessTokenDO extends BaseDO {
/**
* 编号,数据库递增

View File

@@ -2,7 +2,7 @@ package com.njcn.msgpush.module.system.dal.dataobject.oauth2;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.njcn.msgpush.module.system.enums.oauth2.OAuth2GrantTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
@@ -23,7 +23,6 @@ import java.util.List;
@KeySequence("system_oauth2_client_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@TenantIgnore
public class OAuth2ClientDO extends BaseDO {
/**

View File

@@ -1,11 +1,11 @@
package com.njcn.msgpush.module.system.dal.dataobject.oauth2;
import com.njcn.msgpush.framework.common.enums.UserTypeEnum;
import com.njcn.msgpush.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import java.time.LocalDateTime;
@@ -20,7 +20,7 @@ import java.util.List;
// 由于 Oracle 的 SEQ 的名字长度有限制,所以就先用 system_oauth2_access_token_seq 吧,反正也没啥问题
@KeySequence("system_oauth2_access_token_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
public class OAuth2RefreshTokenDO extends TenantBaseDO {
public class OAuth2RefreshTokenDO extends BaseDO {
/**
* 编号,数据库字典

View File

@@ -2,7 +2,7 @@ package com.njcn.msgpush.module.system.dal.dataobject.permission;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.njcn.msgpush.module.system.enums.permission.MenuTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,7 +19,6 @@ import lombok.EqualsAndHashCode;
@KeySequence("system_menu_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@TenantIgnore
public class MenuDO extends BaseDO {
/**

View File

@@ -1,7 +1,7 @@
package com.njcn.msgpush.module.system.dal.dataobject.permission;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.tenant.core.db.TenantBaseDO;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.module.system.enums.permission.DataScopeEnum;
import com.njcn.msgpush.module.system.enums.permission.RoleTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -23,7 +23,7 @@ import java.util.Set;
@KeySequence("system_role_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class RoleDO extends TenantBaseDO {
public class RoleDO extends BaseDO {
/**
* 角色ID

View File

@@ -1,9 +1,9 @@
package com.njcn.msgpush.module.system.dal.dataobject.permission;
import com.njcn.msgpush.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -16,7 +16,7 @@ import lombok.EqualsAndHashCode;
@KeySequence("system_role_menu_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
public class RoleMenuDO extends TenantBaseDO {
public class RoleMenuDO extends BaseDO {
/**
* 自增主键

View File

@@ -1,89 +0,0 @@
package com.njcn.msgpush.module.system.dal.dataobject.tenant;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.framework.mybatis.core.type.StringListTypeHandler;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.njcn.msgpush.module.system.dal.dataobject.user.AdminUserDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* 租户 DO
*
* @author hongawen
*/
@TableName(value = "system_tenant", autoResultMap = true)
@KeySequence("system_tenant_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TenantIgnore
public class TenantDO extends BaseDO {
/**
* 套餐编号 - 系统
*/
public static final Long PACKAGE_ID_SYSTEM = 0L;
/**
* 租户编号,自增
*/
private Long id;
/**
* 租户名,唯一
*/
private String name;
/**
* 联系人的用户编号
*
* 关联 {@link AdminUserDO#getId()}
*/
private Long contactUserId;
/**
* 联系人
*/
private String contactName;
/**
* 联系手机
*/
private String contactMobile;
/**
* 租户状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 绑定域名列表
*
* 1. 考虑到对微信小程序的兼容,也允许传递 appid
* 2. 为什么是数组,考虑到管理后台、会员前台都有独立的域名,又或者多个管理后台
*/
@TableField(typeHandler = StringListTypeHandler.class)
private List<String> websites;
/**
* 租户套餐编号
*
* 关联 {@link TenantPackageDO#getId()}
* 特殊逻辑:系统内置租户,不使用套餐,暂时使用 {@link #PACKAGE_ID_SYSTEM} 标识
*/
private Long packageId;
/**
* 过期时间
*/
private LocalDateTime expireTime;
/**
* 账号数量
*/
private Integer accountCount;
}

View File

@@ -1,54 +0,0 @@
package com.njcn.msgpush.module.system.dal.dataobject.tenant;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.*;
import java.util.Set;
/**
* 租户套餐 DO
*
* @author hongawen
*/
@TableName(value = "system_tenant_package", autoResultMap = true)
@KeySequence("system_tenant_package_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TenantIgnore
public class TenantPackageDO extends BaseDO {
/**
* 套餐编号,自增
*/
private Long id;
/**
* 套餐名,唯一
*/
private String name;
/**
* 租户套餐状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 关联的菜单编号
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private Set<Long> menuIds;
}

View File

@@ -1,7 +1,7 @@
package com.njcn.msgpush.module.system.dal.dataobject.user;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.tenant.core.db.TenantBaseDO;
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.msgpush.module.system.enums.common.SexEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
@@ -26,7 +26,7 @@ import java.util.Set;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AdminUserDO extends TenantBaseDO {
public class AdminUserDO extends BaseDO {
/**
* 用户ID

View File

@@ -3,7 +3,7 @@ package com.njcn.msgpush.module.system.dal.mysql.oauth2;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.msgpush.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.njcn.msgpush.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import org.apache.ibatis.annotations.Mapper;
@@ -14,7 +14,6 @@ import java.util.List;
@Mapper
public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO> {
@TenantIgnore // 获取 token 的时候,需要忽略租户编号。原因是:一些场景下,可能不会传递 tenant-id 请求头,例如说文件上传、积木报表等等
default OAuth2AccessTokenDO selectByAccessToken(String accessToken) {
return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken);
}

View File

@@ -2,7 +2,7 @@ package com.njcn.msgpush.module.system.dal.mysql.oauth2;
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.msgpush.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
import com.njcn.msgpush.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO;
import org.apache.ibatis.annotations.Mapper;
@@ -14,7 +14,6 @@ public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshToken
.eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken));
}
@TenantIgnore // 获取 token 的时候,需要忽略租户编号。原因是:一些场景下,可能不会传递 tenant-id 请求头,例如说文件上传、积木报表等等
default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) {
return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken);
}

View File

@@ -1,47 +0,0 @@
package com.njcn.msgpush.module.system.dal.mysql.tenant;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.msgpush.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.msgpush.framework.mybatis.core.util.MyBatisUtils;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface TenantMapper extends BaseMapperX<TenantDO> {
default PageResult<TenantDO> selectPage(TenantPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<TenantDO>()
.likeIfPresent(TenantDO::getName, reqVO.getName())
.likeIfPresent(TenantDO::getContactName, reqVO.getContactName())
.likeIfPresent(TenantDO::getContactMobile, reqVO.getContactMobile())
.eqIfPresent(TenantDO::getStatus, reqVO.getStatus())
.betweenIfPresent(TenantDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(TenantDO::getId));
}
default TenantDO selectByName(String name) {
return selectOne(TenantDO::getName, name);
}
default List<TenantDO> selectListByWebsite(String website) {
return selectList(new LambdaQueryWrapperX<TenantDO>()
.apply(MyBatisUtils.findInSet("websites", website)));
}
default Long selectCountByPackageId(Long packageId) {
return selectCount(TenantDO::getPackageId, packageId);
}
default List<TenantDO> selectListByPackageId(Long packageId) {
return selectList(TenantDO::getPackageId, packageId);
}
default List<TenantDO> selectListByStatus(Integer status) {
return selectList(TenantDO::getStatus, status);
}
}

View File

@@ -1,31 +0,0 @@
package com.njcn.msgpush.module.system.dal.mysql.tenant;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.msgpush.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantPackageDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface TenantPackageMapper extends BaseMapperX<TenantPackageDO> {
default PageResult<TenantPackageDO> selectPage(TenantPackagePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<TenantPackageDO>()
.likeIfPresent(TenantPackageDO::getName, reqVO.getName())
.eqIfPresent(TenantPackageDO::getStatus, reqVO.getStatus())
.likeIfPresent(TenantPackageDO::getRemark, reqVO.getRemark())
.betweenIfPresent(TenantPackageDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(TenantPackageDO::getId));
}
default List<TenantPackageDO> selectListByStatus(Integer status) {
return selectList(TenantPackageDO::getStatus, status);
}
default TenantPackageDO selectByName(String name) {
return selectOne(TenantPackageDO::getName, name);
}
}

View File

@@ -11,8 +11,6 @@ import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.date.DateUtils;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.framework.security.core.LoginUser;
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
import com.njcn.msgpush.framework.tenant.core.util.TenantUtils;
import com.njcn.msgpush.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.njcn.msgpush.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
@@ -180,13 +178,6 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
.setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes())
.setRefreshToken(refreshTokenDO.getRefreshToken())
.setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds()));
// 优先从 refreshToken 获取租户编号,避免 ThreadLocal 被污染时导致 tenantId 为 null
// 可能关联的 issuehttps://t.zsxq.com/JIi5G
Long tenantId = refreshTokenDO.getTenantId();
if (tenantId == null) {
tenantId = TenantContextHolder.getTenantId();
}
accessTokenDO.setTenantId(tenantId);
oauth2AccessTokenMapper.insert(accessTokenDO);
// 记录到 Redis 中
oauth2AccessTokenRedisDAO.set(accessTokenDO);
@@ -205,8 +196,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
private OAuth2AccessTokenDO convertToAccessToken(OAuth2RefreshTokenDO refreshTokenDO) {
OAuth2AccessTokenDO accessTokenDO = BeanUtils.toBean(refreshTokenDO, OAuth2AccessTokenDO.class)
.setAccessToken(refreshTokenDO.getRefreshToken());
TenantUtils.execute(refreshTokenDO.getTenantId(),
() -> accessTokenDO.setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType())));
accessTokenDO.setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType()));
return accessTokenDO;
}

View File

@@ -50,14 +50,6 @@ public interface MenuService {
*/
List<MenuDO> getMenuList();
/**
* 基于租户,筛选菜单列表
* 注意,如果是系统租户,返回的还是全菜单
*
* @param reqVO 筛选条件请求 VO
* @return 菜单列表
*/
List<MenuDO> getMenuListByTenant(MenuListReqVO reqVO);
/**
* 过滤掉关闭的菜单及其子菜单

View File

@@ -11,7 +11,6 @@ import com.njcn.msgpush.module.system.dal.dataobject.permission.MenuDO;
import com.njcn.msgpush.module.system.dal.mysql.permission.MenuMapper;
import com.njcn.msgpush.module.system.dal.redis.RedisKeyConstants;
import com.njcn.msgpush.module.system.enums.permission.MenuTypeEnum;
import com.njcn.msgpush.module.system.service.tenant.TenantService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import jakarta.annotation.Resource;
@@ -43,9 +42,6 @@ public class MenuServiceImpl implements MenuService {
private MenuMapper menuMapper;
@Resource
private PermissionService permissionService;
@Resource
@Lazy // 延迟,避免循环依赖报错
private TenantService tenantService;
@Override
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#createReqVO.permission",
@@ -127,15 +123,6 @@ public class MenuServiceImpl implements MenuService {
return menuMapper.selectList();
}
@Override
public List<MenuDO> getMenuListByTenant(MenuListReqVO reqVO) {
// 查询所有菜单,并过滤掉关闭的节点
List<MenuDO> menus = getMenuList(reqVO);
// 开启多租户的情况下,需要过滤掉未开通的菜单
tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId())));
return menus;
}
@Override
public List<MenuDO> filterDisableMenus(List<MenuDO> menuList) {
if (CollUtil.isEmpty(menuList)){

View File

@@ -1,79 +0,0 @@
package com.njcn.msgpush.module.system.service.tenant;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantPackageDO;
import jakarta.validation.Valid;
import java.util.List;
/**
* 租户套餐 Service 接口
*
* @author hongawen
*/
public interface TenantPackageService {
/**
* 创建租户套餐
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createTenantPackage(@Valid TenantPackageSaveReqVO createReqVO);
/**
* 更新租户套餐
*
* @param updateReqVO 更新信息
*/
void updateTenantPackage(@Valid TenantPackageSaveReqVO updateReqVO);
/**
* 删除租户套餐
*
* @param id 编号
*/
void deleteTenantPackage(Long id);
/**
* 批量删除租户套餐
*
* @param ids 编号数组
*/
void deleteTenantPackageList(List<Long> ids);
/**
* 获得租户套餐
*
* @param id 编号
* @return 租户套餐
*/
TenantPackageDO getTenantPackage(Long id);
/**
* 获得租户套餐分页
*
* @param pageReqVO 分页查询
* @return 租户套餐分页
*/
PageResult<TenantPackageDO> getTenantPackagePage(TenantPackagePageReqVO pageReqVO);
/**
* 校验租户套餐
*
* @param id 编号
* @return 租户套餐
*/
TenantPackageDO validTenantPackage(Long id);
/**
* 获得指定状态的租户套餐列表
*
* @param status 状态
* @return 租户套餐
*/
List<TenantPackageDO> getTenantPackageListByStatus(Integer status);
}

View File

@@ -1,152 +0,0 @@
package com.njcn.msgpush.module.system.service.tenant;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantDO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantPackageDO;
import com.njcn.msgpush.module.system.dal.mysql.tenant.TenantPackageMapper;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import static com.njcn.msgpush.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.msgpush.module.system.enums.ErrorCodeConstants.*;
/**
* 租户套餐 Service 实现类
*
* @author hongawen
*/
@Service
@Validated
public class TenantPackageServiceImpl implements TenantPackageService {
@Resource
private TenantPackageMapper tenantPackageMapper;
@Resource
@Lazy // 避免循环依赖的报错
private TenantService tenantService;
@Override
public Long createTenantPackage(TenantPackageSaveReqVO createReqVO) {
// 校验套餐名是否重复
validateTenantPackageNameUnique(null, createReqVO.getName());
// 插入
TenantPackageDO tenantPackage = BeanUtils.toBean(createReqVO, TenantPackageDO.class);
tenantPackageMapper.insert(tenantPackage);
// 返回
return tenantPackage.getId();
}
@Override
@DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
public void updateTenantPackage(TenantPackageSaveReqVO updateReqVO) {
// 校验存在
TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId());
// 校验套餐名是否重复
validateTenantPackageNameUnique(updateReqVO.getId(), updateReqVO.getName());
// 更新
TenantPackageDO updateObj = BeanUtils.toBean(updateReqVO, TenantPackageDO.class);
tenantPackageMapper.updateById(updateObj);
// 如果菜单发生变化,则修改每个租户的菜单
if (!CollUtil.isEqualList(tenantPackage.getMenuIds(), updateReqVO.getMenuIds())) {
List<TenantDO> tenants = tenantService.getTenantListByPackageId(tenantPackage.getId());
tenants.forEach(tenant -> tenantService.updateTenantRoleMenu(tenant.getId(), updateReqVO.getMenuIds()));
}
}
@Override
public void deleteTenantPackage(Long id) {
// 校验存在
validateTenantPackageExists(id);
// 校验正在使用
validateTenantUsed(id);
// 删除
tenantPackageMapper.deleteById(id);
}
@Override
public void deleteTenantPackageList(List<Long> ids) {
// 1. 校验是否有租户正在使用该套餐
for (Long id : ids) {
if (tenantService.getTenantCountByPackageId(id) > 0) {
throw exception(TENANT_PACKAGE_USED);
}
}
// 2. 批量删除
tenantPackageMapper.deleteByIds(ids);
}
private TenantPackageDO validateTenantPackageExists(Long id) {
TenantPackageDO tenantPackage = tenantPackageMapper.selectById(id);
if (tenantPackage == null) {
throw exception(TENANT_PACKAGE_NOT_EXISTS);
}
return tenantPackage;
}
private void validateTenantUsed(Long id) {
if (tenantService.getTenantCountByPackageId(id) > 0) {
throw exception(TENANT_PACKAGE_USED);
}
}
@Override
public TenantPackageDO getTenantPackage(Long id) {
return tenantPackageMapper.selectById(id);
}
@Override
public PageResult<TenantPackageDO> getTenantPackagePage(TenantPackagePageReqVO pageReqVO) {
return tenantPackageMapper.selectPage(pageReqVO);
}
@Override
public TenantPackageDO validTenantPackage(Long id) {
TenantPackageDO tenantPackage = tenantPackageMapper.selectById(id);
if (tenantPackage == null) {
throw exception(TENANT_PACKAGE_NOT_EXISTS);
}
if (tenantPackage.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) {
throw exception(TENANT_PACKAGE_DISABLE, tenantPackage.getName());
}
return tenantPackage;
}
@Override
public List<TenantPackageDO> getTenantPackageListByStatus(Integer status) {
return tenantPackageMapper.selectListByStatus(status);
}
@VisibleForTesting
void validateTenantPackageNameUnique(Long id, String name) {
if (StrUtil.isBlank(name)) {
return;
}
TenantPackageDO tenantPackage = tenantPackageMapper.selectByName(name);
if (tenantPackage == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的用户
if (id == null) {
throw exception(TENANT_PACKAGE_NAME_DUPLICATE);
}
if (!tenantPackage.getId().equals(id)) {
throw exception(TENANT_PACKAGE_NAME_DUPLICATE);
}
}
}

View File

@@ -1,145 +0,0 @@
package com.njcn.msgpush.module.system.service.tenant;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantDO;
import com.njcn.msgpush.module.system.service.tenant.handler.TenantInfoHandler;
import com.njcn.msgpush.module.system.service.tenant.handler.TenantMenuHandler;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Set;
/**
* 租户 Service 接口
*
* @author hongawen
*/
public interface TenantService {
/**
* 创建租户
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createTenant(@Valid TenantSaveReqVO createReqVO);
/**
* 更新租户
*
* @param updateReqVO 更新信息
*/
void updateTenant(@Valid TenantSaveReqVO updateReqVO);
/**
* 更新租户的角色菜单
*
* @param tenantId 租户编号
* @param menuIds 菜单编号数组
*/
void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds);
/**
* 删除租户
*
* @param id 编号
*/
void deleteTenant(Long id);
/**
* 批量删除租户
*
* @param ids 编号数组
*/
void deleteTenantList(List<Long> ids);
/**
* 获得租户
*
* @param id 编号
* @return 租户
*/
TenantDO getTenant(Long id);
/**
* 获得租户分页
*
* @param pageReqVO 分页查询
* @return 租户分页
*/
PageResult<TenantDO> getTenantPage(TenantPageReqVO pageReqVO);
/**
* 获得名字对应的租户
*
* @param name 租户名
* @return 租户
*/
TenantDO getTenantByName(String name);
/**
* 获得域名对应的租户
*
* @param website 域名
* @return 租户
*/
TenantDO getTenantByWebsite(String website);
/**
* 获得使用指定套餐的租户数量
*
* @param packageId 租户套餐编号
* @return 租户数量
*/
Long getTenantCountByPackageId(Long packageId);
/**
* 获得使用指定套餐的租户数组
*
* @param packageId 租户套餐编号
* @return 租户数组
*/
List<TenantDO> getTenantListByPackageId(Long packageId);
/**
* 获得指定状态的租户列表
*
* @param status 状态
* @return 租户列表
*/
List<TenantDO> getTenantListByStatus(Integer status);
/**
* 进行租户的信息处理逻辑
* 其中,租户编号从 {@link TenantContextHolder} 上下文中获取
*
* @param handler 处理器
*/
void handleTenantInfo(TenantInfoHandler handler);
/**
* 进行租户的菜单处理逻辑
* 其中,租户编号从 {@link TenantContextHolder} 上下文中获取
*
* @param handler 处理器
*/
void handleTenantMenu(TenantMenuHandler handler);
/**
* 获得所有租户
*
* @return 租户编号数组
*/
List<Long> getTenantIdList();
/**
* 校验租户是否合法
*
* @param id 租户编号
*/
void validTenant(Long id);
}

View File

@@ -1,318 +0,0 @@
package com.njcn.msgpush.module.system.service.tenant;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.collection.CollectionUtils;
import com.njcn.msgpush.framework.common.util.date.DateUtils;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.framework.tenant.config.TenantProperties;
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
import com.njcn.msgpush.framework.tenant.core.util.TenantUtils;
import com.njcn.msgpush.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;
import com.njcn.msgpush.module.system.convert.tenant.TenantConvert;
import com.njcn.msgpush.module.system.dal.dataobject.permission.MenuDO;
import com.njcn.msgpush.module.system.dal.dataobject.permission.RoleDO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantDO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantPackageDO;
import com.njcn.msgpush.module.system.dal.mysql.tenant.TenantMapper;
import com.njcn.msgpush.module.system.enums.permission.RoleCodeEnum;
import com.njcn.msgpush.module.system.enums.permission.RoleTypeEnum;
import com.njcn.msgpush.module.system.service.permission.MenuService;
import com.njcn.msgpush.module.system.service.permission.PermissionService;
import com.njcn.msgpush.module.system.service.permission.RoleService;
import com.njcn.msgpush.module.system.service.tenant.handler.TenantInfoHandler;
import com.njcn.msgpush.module.system.service.tenant.handler.TenantMenuHandler;
import com.njcn.msgpush.module.system.service.user.AdminUserService;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import static com.njcn.msgpush.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.msgpush.module.system.enums.ErrorCodeConstants.*;
import static java.util.Collections.singleton;
/**
* 租户 Service 实现类
*
* @author hongawen
*/
@Service
@Validated
@Slf4j
public class TenantServiceImpl implements TenantService {
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
@Autowired(required = false) // 由于 msgpush.tenant.enable 配置项,可以关闭多租户的功能,所以这里只能不强制注入
private TenantProperties tenantProperties;
@Resource
private TenantMapper tenantMapper;
@Resource
private TenantPackageService tenantPackageService;
@Resource
@Lazy // 延迟,避免循环依赖报错
private AdminUserService userService;
@Resource
private RoleService roleService;
@Resource
private MenuService menuService;
@Resource
private PermissionService permissionService;
@Override
public List<Long> getTenantIdList() {
List<TenantDO> tenants = tenantMapper.selectList();
return CollectionUtils.convertList(tenants, TenantDO::getId);
}
@Override
public void validTenant(Long id) {
TenantDO tenant = getTenant(id);
if (tenant == null) {
throw exception(TENANT_NOT_EXISTS);
}
if (tenant.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) {
throw exception(TENANT_DISABLE, tenant.getName());
}
if (DateUtils.isExpired(tenant.getExpireTime())) {
throw exception(TENANT_EXPIRE, tenant.getName());
}
}
@Override
@DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
public Long createTenant(TenantSaveReqVO createReqVO) {
// 校验租户名称是否重复
validTenantNameDuplicate(createReqVO.getName(), null);
// 校验租户域名是否重复
validTenantWebsiteDuplicate(createReqVO.getWebsites(), null);
// 校验套餐被禁用
TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId());
// 创建租户
TenantDO tenant = BeanUtils.toBean(createReqVO, TenantDO.class);
tenantMapper.insert(tenant);
// 创建租户的管理员
TenantUtils.execute(tenant.getId(), () -> {
// 创建角色
Long roleId = createRole(tenantPackage);
// 创建用户,并分配角色
Long userId = createUser(roleId, createReqVO);
// 修改租户的管理员
tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId));
});
return tenant.getId();
}
private Long createUser(Long roleId, TenantSaveReqVO createReqVO) {
// 创建用户
Long userId = userService.createUser(TenantConvert.INSTANCE.convert02(createReqVO));
// 分配角色
permissionService.assignUserRole(userId, singleton(roleId));
return userId;
}
private Long createRole(TenantPackageDO tenantPackage) {
// 创建角色
RoleSaveReqVO reqVO = new RoleSaveReqVO();
reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName()).setCode(RoleCodeEnum.TENANT_ADMIN.getCode())
.setSort(0).setRemark("系统自动生成");
Long roleId = roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType());
// 分配权限
permissionService.assignRoleMenu(roleId, tenantPackage.getMenuIds());
return roleId;
}
@Override
@DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
public void updateTenant(TenantSaveReqVO updateReqVO) {
// 校验存在
TenantDO tenant = validateUpdateTenant(updateReqVO.getId());
// 校验租户名称是否重复
validTenantNameDuplicate(updateReqVO.getName(), updateReqVO.getId());
// 校验租户域名是否重复
validTenantWebsiteDuplicate(updateReqVO.getWebsites(), updateReqVO.getId());
// 校验套餐被禁用
TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(updateReqVO.getPackageId());
// 更新租户
TenantDO updateObj = BeanUtils.toBean(updateReqVO, TenantDO.class);
tenantMapper.updateById(updateObj);
// 如果套餐发生变化,则修改其角色的权限
if (ObjectUtil.notEqual(tenant.getPackageId(), updateReqVO.getPackageId())) {
updateTenantRoleMenu(tenant.getId(), tenantPackage.getMenuIds());
}
}
private void validTenantNameDuplicate(String name, Long id) {
TenantDO tenant = tenantMapper.selectByName(name);
if (tenant == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同名字的租户
if (id == null) {
throw exception(TENANT_NAME_DUPLICATE, name);
}
if (!tenant.getId().equals(id)) {
throw exception(TENANT_NAME_DUPLICATE, name);
}
}
private void validTenantWebsiteDuplicate(List<String> websites, Long excludeId) {
if (CollUtil.isEmpty(websites)) {
return;
}
websites.forEach(website -> {
List<TenantDO> tenants = tenantMapper.selectListByWebsite(website);
if (excludeId != null) {
tenants.removeIf(tenant -> tenant.getId().equals(excludeId));
}
if (CollUtil.isNotEmpty(tenants)) {
throw exception(TENANT_WEBSITE_DUPLICATE, website);
}
});
}
@Override
@DSTransactional
public void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds) {
TenantUtils.execute(tenantId, () -> {
// 获得所有角色
List<RoleDO> roles = roleService.getRoleList();
roles.forEach(role -> Assert.isTrue(tenantId.equals(role.getTenantId()), "角色({}/{}) 租户不匹配",
role.getId(), role.getTenantId(), tenantId)); // 兜底校验
// 重新分配每个角色的权限
roles.forEach(role -> {
// 如果是租户管理员,重新分配其权限为租户套餐的权限
if (Objects.equals(role.getCode(), RoleCodeEnum.TENANT_ADMIN.getCode())) {
permissionService.assignRoleMenu(role.getId(), menuIds);
log.info("[updateTenantRoleMenu][租户管理员({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), menuIds);
return;
}
// 如果是其他角色,则去掉超过套餐的权限
Set<Long> roleMenuIds = permissionService.getRoleMenuListByRoleId(role.getId());
roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds);
permissionService.assignRoleMenu(role.getId(), roleMenuIds);
log.info("[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), roleMenuIds);
});
});
}
@Override
public void deleteTenant(Long id) {
// 校验存在
validateUpdateTenant(id);
// 删除
tenantMapper.deleteById(id);
}
@Override
public void deleteTenantList(List<Long> ids) {
// 1. 校验存在
ids.forEach(this::validateUpdateTenant);
// 2. 批量删除
tenantMapper.deleteByIds(ids);
}
private TenantDO validateUpdateTenant(Long id) {
TenantDO tenant = tenantMapper.selectById(id);
if (tenant == null) {
throw exception(TENANT_NOT_EXISTS);
}
// 内置租户,不允许删除
if (isSystemTenant(tenant)) {
throw exception(TENANT_CAN_NOT_UPDATE_SYSTEM);
}
return tenant;
}
@Override
public TenantDO getTenant(Long id) {
return tenantMapper.selectById(id);
}
@Override
public PageResult<TenantDO> getTenantPage(TenantPageReqVO pageReqVO) {
return tenantMapper.selectPage(pageReqVO);
}
@Override
public TenantDO getTenantByName(String name) {
return tenantMapper.selectByName(name);
}
@Override
public TenantDO getTenantByWebsite(String website) {
List<TenantDO> tenants = tenantMapper.selectListByWebsite(website);
return CollUtil.getFirst(tenants);
}
@Override
public Long getTenantCountByPackageId(Long packageId) {
return tenantMapper.selectCountByPackageId(packageId);
}
@Override
public List<TenantDO> getTenantListByPackageId(Long packageId) {
return tenantMapper.selectListByPackageId(packageId);
}
@Override
public List<TenantDO> getTenantListByStatus(Integer status) {
return tenantMapper.selectListByStatus(status);
}
@Override
public void handleTenantInfo(TenantInfoHandler handler) {
// 如果禁用,则不执行逻辑
if (isTenantDisable()) {
return;
}
// 获得租户
TenantDO tenant = getTenant(TenantContextHolder.getRequiredTenantId());
// 执行处理器
handler.handle(tenant);
}
@Override
public void handleTenantMenu(TenantMenuHandler handler) {
// 如果禁用,则不执行逻辑
if (isTenantDisable()) {
return;
}
// 获得租户,然后获得菜单
TenantDO tenant = getTenant(TenantContextHolder.getRequiredTenantId());
Set<Long> menuIds;
if (isSystemTenant(tenant)) { // 系统租户,菜单是全量的
menuIds = CollectionUtils.convertSet(menuService.getMenuList(), MenuDO::getId);
} else {
menuIds = tenantPackageService.getTenantPackage(tenant.getPackageId()).getMenuIds();
}
// 执行处理器
handler.handle(menuIds);
}
private static boolean isSystemTenant(TenantDO tenant) {
return Objects.equals(tenant.getPackageId(), TenantDO.PACKAGE_ID_SYSTEM);
}
private boolean isTenantDisable() {
return tenantProperties == null || Boolean.FALSE.equals(tenantProperties.getEnable());
}
}

View File

@@ -1,21 +0,0 @@
package com.njcn.msgpush.module.system.service.tenant.handler;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantDO;
/**
* 租户信息处理
* 目的:尽量减少租户逻辑耦合到系统中
*
* @author hongawen
*/
public interface TenantInfoHandler {
/**
* 基于传入的租户信息,进行相关逻辑的执行
* 例如说,创建用户时,超过最大账户配额
*
* @param tenant 租户信息
*/
void handle(TenantDO tenant);
}

View File

@@ -1,21 +0,0 @@
package com.njcn.msgpush.module.system.service.tenant.handler;
import java.util.Set;
/**
* 租户菜单处理
* 目的:尽量减少租户逻辑耦合到系统中
*
* @author hongawen
*/
public interface TenantMenuHandler {
/**
* 基于传入的租户菜单【全】列表,进行相关逻辑的执行
* 例如说,返回可分配菜单的时候,可以移除多余的
*
* @param menuIds 菜单列表
*/
void handle(Set<Long> menuIds);
}

View File

@@ -28,7 +28,6 @@ import com.njcn.msgpush.module.system.service.dept.DeptService;
import com.njcn.msgpush.module.system.service.dept.PostService;
import com.njcn.msgpush.module.system.service.oauth2.OAuth2TokenService;
import com.njcn.msgpush.module.system.service.permission.PermissionService;
import com.njcn.msgpush.module.system.service.tenant.TenantService;
import com.google.common.annotations.VisibleForTesting;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
@@ -74,9 +73,6 @@ public class AdminUserServiceImpl implements AdminUserService {
@Resource
private PasswordEncoder passwordEncoder;
@Resource
@Lazy // 延迟,避免循环依赖报错
private TenantService tenantService;
@Resource
@Lazy // 懒加载,避免循环依赖
private OAuth2TokenService oauth2TokenService;
@@ -91,14 +87,7 @@ public class AdminUserServiceImpl implements AdminUserService {
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
success = SYSTEM_USER_CREATE_SUCCESS)
public Long createUser(UserSaveReqVO createReqVO) {
// 1.1 校验账户配合
tenantService.handleTenantInfo(tenant -> {
long count = userMapper.selectCount();
if (count >= tenant.getAccountCount()) {
throw exception(USER_COUNT_MAX, tenant.getAccountCount());
}
});
// 1.2 校验正确性
// 1.1 校验正确性
validateUserForCreateOrUpdate(null, createReqVO.getUsername(),
createReqVO.getMobile(), createReqVO.getEmail(), createReqVO.getDeptId(), createReqVO.getPostIds());
// 2.1 插入用户
@@ -123,14 +112,7 @@ public class AdminUserServiceImpl implements AdminUserService {
if (ObjUtil.notEqual(configApi.getConfigValueByKey(USER_REGISTER_ENABLED_KEY).getCheckedData(), "true")) {
throw exception(USER_REGISTER_DISABLED);
}
// 1.2 校验账户配合
tenantService.handleTenantInfo(tenant -> {
long count = userMapper.selectCount();
if (count >= tenant.getAccountCount()) {
throw exception(USER_COUNT_MAX, tenant.getAccountCount());
}
});
// 1.3 校验正确性
// 1.2 校验正确性
validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null);
// 2. 插入用户

View File

@@ -126,20 +126,6 @@ msgpush:
title: 管理后台
description: 提供管理员管理的所有功能
version: ${msgpush.info.version}
tenant:
enable: true
ignore-urls:
ignore-visit-urls:
- /admin-api/system/user/profile/**
- /admin-api/system/auth/**
ignore-tables:
ignore-caches:
- user_role_ids
- permission_menu_ids
- oauth_client
- notify_template
- mail_account
- mail_template
- sms_template
debug: false

View File

@@ -5,7 +5,6 @@ import com.njcn.msgpush.framework.common.enums.UserTypeEnum;
import com.njcn.msgpush.framework.common.exception.ErrorCode;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.date.DateUtils;
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
import com.njcn.msgpush.framework.test.core.ut.BaseDbAndRedisUnitTest;
import com.njcn.msgpush.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
@@ -59,7 +58,6 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
@Test
public void testCreateAccessToken() {
TenantContextHolder.setTenantId(0L);
// 准备参数
Long userId = randomLongId();
Integer userType = UserTypeEnum.ADMIN.getValue();
@@ -149,7 +147,6 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
@Test
public void testRefreshAccessToken_success() {
TenantContextHolder.setTenantId(0L);
// 准备参数
String refreshToken = randomString();
String clientId = randomString();
@@ -161,8 +158,7 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class, o ->
o.setRefreshToken(refreshToken).setClientId(clientId)
.setExpiresTime(LocalDateTime.now().plusDays(1))
.setUserType(UserTypeEnum.ADMIN.getValue())
.setTenantId(TenantContextHolder.getTenantId()));
.setUserType(UserTypeEnum.ADMIN.getValue()));
oauth2RefreshTokenMapper.insert(refreshTokenDO);
// mock 数据(访问令牌)
OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class).setRefreshToken(refreshToken)

View File

@@ -7,7 +7,6 @@ import com.njcn.msgpush.module.system.controller.admin.permission.vo.menu.MenuSa
import com.njcn.msgpush.module.system.dal.dataobject.permission.MenuDO;
import com.njcn.msgpush.module.system.dal.mysql.permission.MenuMapper;
import com.njcn.msgpush.module.system.enums.permission.MenuTypeEnum;
import com.njcn.msgpush.module.system.service.tenant.TenantService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
@@ -42,8 +41,6 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
@MockitoBean
private PermissionService permissionService;
@MockitoBean
private TenantService tenantService;
@Test
public void testCreateMenu_success() {
@@ -162,30 +159,6 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
assertPojoEquals(menuDO, result.get(0));
}
@Test
public void testGetMenuListByTenant() {
// mock 数据
MenuDO menu100 = randomPojo(MenuDO.class, o -> o.setId(100L).setStatus(CommonStatusEnum.ENABLE.getStatus()));
menuMapper.insert(menu100);
MenuDO menu101 = randomPojo(MenuDO.class, o -> o.setId(101L).setStatus(CommonStatusEnum.DISABLE.getStatus()));
menuMapper.insert(menu101);
MenuDO menu102 = randomPojo(MenuDO.class, o -> o.setId(102L).setStatus(CommonStatusEnum.ENABLE.getStatus()));
menuMapper.insert(menu102);
// mock 过滤菜单
Set<Long> menuIds = asSet(100L, 101L);
doNothing().when(tenantService).handleTenantMenu(argThat(handler -> {
handler.handle(menuIds);
return true;
}));
// 准备参数
MenuListReqVO reqVO = new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus());
// 调用
List<MenuDO> result = menuService.getMenuListByTenant(reqVO);
// 断言
assertEquals(1, result.size());
assertPojoEquals(menu100, result.get(0));
}
@Test
public void testGetMenuIdListByPermissionFromCache() {

View File

@@ -320,23 +320,6 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
}
}
@Test
public void testHasAnySuperAdmin_false() {
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
.thenReturn(roleService);
// mock 数据
RoleDO dbRole = randomPojo(RoleDO.class).setCode("tenant_admin");
roleMapper.insert(dbRole);
// 准备参数
Long id = dbRole.getId();
// 调用,并调用
assertFalse(roleService.hasAnySuperAdmin(singletonList(id)));
}
}
@Test
public void testValidateRoleList_success() {
// mock 数据

View File

@@ -1,237 +0,0 @@
package com.njcn.msgpush.module.system.service.tenant;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.test.core.ut.BaseDbUnitTest;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantDO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantPackageDO;
import com.njcn.msgpush.module.system.dal.mysql.tenant.TenantPackageMapper;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import jakarta.annotation.Resource;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.util.List;
import static com.njcn.msgpush.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static com.njcn.msgpush.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static com.njcn.msgpush.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static com.njcn.msgpush.framework.test.core.util.AssertUtils.assertPojoEquals;
import static com.njcn.msgpush.framework.test.core.util.AssertUtils.assertServiceException;
import static com.njcn.msgpush.framework.test.core.util.RandomUtils.*;
import static com.njcn.msgpush.module.system.enums.ErrorCodeConstants.*;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link TenantPackageServiceImpl} 的单元测试类
*
* @author hongawen
*/
@Import(TenantPackageServiceImpl.class)
public class TenantPackageServiceImplTest extends BaseDbUnitTest {
@Resource
private TenantPackageServiceImpl tenantPackageService;
@Resource
private TenantPackageMapper tenantPackageMapper;
@MockitoBean
private TenantService tenantService;
@Test
public void testCreateTenantPackage_success() {
// 准备参数
TenantPackageSaveReqVO reqVO = randomPojo(TenantPackageSaveReqVO.class,
o -> o.setStatus(randomCommonStatus()))
.setId(null); // 防止 id 被赋值
// 调用
Long tenantPackageId = tenantPackageService.createTenantPackage(reqVO);
// 断言
assertNotNull(tenantPackageId);
// 校验记录的属性是否正确
TenantPackageDO tenantPackage = tenantPackageMapper.selectById(tenantPackageId);
assertPojoEquals(reqVO, tenantPackage, "id");
}
@Test
public void testUpdateTenantPackage_success() {
// mock 数据
TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class,
o -> o.setStatus(randomCommonStatus()));
tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据
// 准备参数
TenantPackageSaveReqVO reqVO = randomPojo(TenantPackageSaveReqVO.class, o -> {
o.setId(dbTenantPackage.getId()); // 设置更新的 ID
o.setStatus(randomCommonStatus());
});
// mock 方法
Long tenantId01 = randomLongId();
Long tenantId02 = randomLongId();
when(tenantService.getTenantListByPackageId(eq(reqVO.getId()))).thenReturn(
asList(randomPojo(TenantDO.class, o -> o.setId(tenantId01)),
randomPojo(TenantDO.class, o -> o.setId(tenantId02))));
// 调用
tenantPackageService.updateTenantPackage(reqVO);
// 校验是否更新正确
TenantPackageDO tenantPackage = tenantPackageMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, tenantPackage);
// 校验调用租户的菜单
verify(tenantService).updateTenantRoleMenu(eq(tenantId01), eq(reqVO.getMenuIds()));
verify(tenantService).updateTenantRoleMenu(eq(tenantId02), eq(reqVO.getMenuIds()));
}
@Test
public void testUpdateTenantPackage_notExists() {
// 准备参数
TenantPackageSaveReqVO reqVO = randomPojo(TenantPackageSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> tenantPackageService.updateTenantPackage(reqVO), TENANT_PACKAGE_NOT_EXISTS);
}
@Test
public void testDeleteTenantPackage_success() {
// mock 数据
TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class);
tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbTenantPackage.getId();
// mock 租户未使用该套餐
when(tenantService.getTenantCountByPackageId(eq(id))).thenReturn(0L);
// 调用
tenantPackageService.deleteTenantPackage(id);
// 校验数据不存在了
assertNull(tenantPackageMapper.selectById(id));
}
@Test
public void testDeleteTenantPackage_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> tenantPackageService.deleteTenantPackage(id), TENANT_PACKAGE_NOT_EXISTS);
}
@Test
public void testDeleteTenantPackage_used() {
// mock 数据
TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class);
tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbTenantPackage.getId();
// mock 租户在使用该套餐
when(tenantService.getTenantCountByPackageId(eq(id))).thenReturn(1L);
// 调用, 并断言异常
assertServiceException(() -> tenantPackageService.deleteTenantPackage(id), TENANT_PACKAGE_USED);
}
@Test
public void testGetTenantPackagePage() {
// mock 数据
TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class, o -> { // 等会查询到
o.setName("灿能源码");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setRemark("源码解析");
o.setCreateTime(buildTime(2022, 10, 10));
});
tenantPackageMapper.insert(dbTenantPackage);
// 测试 name 不匹配
tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setName("源码")));
// 测试 status 不匹配
tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 测试 remark 不匹配
tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setRemark("解析")));
// 测试 createTime 不匹配
tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setCreateTime(buildTime(2022, 11, 11))));
// 准备参数
TenantPackagePageReqVO reqVO = new TenantPackagePageReqVO();
reqVO.setName("灿能");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
reqVO.setRemark("源码");
reqVO.setCreateTime(buildBetweenTime(2022, 10, 9, 2022, 10, 11));
// 调用
PageResult<TenantPackageDO> pageResult = tenantPackageService.getTenantPackagePage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbTenantPackage, pageResult.getList().get(0));
}
@Test
public void testValidTenantPackage_success() {
// mock 数据
TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class,
o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据
// 调用
TenantPackageDO result = tenantPackageService.validTenantPackage(dbTenantPackage.getId());
// 断言
assertPojoEquals(dbTenantPackage, result);
}
@Test
public void testValidTenantPackage_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> tenantPackageService.validTenantPackage(id), TENANT_PACKAGE_NOT_EXISTS);
}
@Test
public void testValidTenantPackage_disable() {
// mock 数据
TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class,
o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据
// 调用, 并断言异常
assertServiceException(() -> tenantPackageService.validTenantPackage(dbTenantPackage.getId()),
TENANT_PACKAGE_DISABLE, dbTenantPackage.getName());
}
@Test
public void testGetTenantPackage() {
// mock 数据
TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class);
tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据
// 调用
TenantPackageDO result = tenantPackageService.getTenantPackage(dbTenantPackage.getId());
// 断言
assertPojoEquals(result, dbTenantPackage);
}
@Test
public void testGetTenantPackageListByStatus() {
// mock 数据
TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class,
o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
tenantPackageMapper.insert(dbTenantPackage);
// 测试 status 不匹配
tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage,
o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 调用
List<TenantPackageDO> list = tenantPackageService.getTenantPackageListByStatus(
CommonStatusEnum.ENABLE.getStatus());
assertEquals(1, list.size());
assertPojoEquals(dbTenantPackage, list.get(0));
}
}

View File

@@ -1,461 +0,0 @@
package com.njcn.msgpush.module.system.service.tenant;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.tenant.config.TenantProperties;
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
import com.njcn.msgpush.framework.test.core.ut.BaseDbUnitTest;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
import com.njcn.msgpush.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.permission.MenuDO;
import com.njcn.msgpush.module.system.dal.dataobject.permission.RoleDO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantDO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantPackageDO;
import com.njcn.msgpush.module.system.dal.mysql.tenant.TenantMapper;
import com.njcn.msgpush.module.system.enums.permission.RoleCodeEnum;
import com.njcn.msgpush.module.system.enums.permission.RoleTypeEnum;
import com.njcn.msgpush.module.system.service.permission.MenuService;
import com.njcn.msgpush.module.system.service.permission.PermissionService;
import com.njcn.msgpush.module.system.service.permission.RoleService;
import com.njcn.msgpush.module.system.service.tenant.handler.TenantInfoHandler;
import com.njcn.msgpush.module.system.service.tenant.handler.TenantMenuHandler;
import com.njcn.msgpush.module.system.service.user.AdminUserService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.njcn.msgpush.framework.common.util.collection.SetUtils.asSet;
import static com.njcn.msgpush.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static com.njcn.msgpush.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static com.njcn.msgpush.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static com.njcn.msgpush.framework.test.core.util.AssertUtils.assertPojoEquals;
import static com.njcn.msgpush.framework.test.core.util.AssertUtils.assertServiceException;
import static com.njcn.msgpush.framework.test.core.util.RandomUtils.*;
import static com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantDO.PACKAGE_ID_SYSTEM;
import static com.njcn.msgpush.module.system.enums.ErrorCodeConstants.*;
import static java.util.Arrays.asList;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
/**
* {@link TenantServiceImpl} 的单元测试类
*
* @author hongawen
*/
@Import(TenantServiceImpl.class)
public class TenantServiceImplTest extends BaseDbUnitTest {
@Resource
private TenantServiceImpl tenantService;
@Resource
private TenantMapper tenantMapper;
@MockitoBean
private TenantProperties tenantProperties;
@MockitoBean
private TenantPackageService tenantPackageService;
@MockitoBean
private AdminUserService userService;
@MockitoBean
private RoleService roleService;
@MockitoBean
private MenuService menuService;
@MockitoBean
private PermissionService permissionService;
@BeforeEach
public void setUp() {
// 清理租户上下文
TenantContextHolder.clear();
}
@Test
public void testGetTenantIdList() {
// mock 数据
TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L));
tenantMapper.insert(tenant);
// 调用,并断言业务异常
List<Long> result = tenantService.getTenantIdList();
assertEquals(Collections.singletonList(1L), result);
}
@Test
public void testValidTenant_notExists() {
assertServiceException(() -> tenantService.validTenant(randomLongId()), TENANT_NOT_EXISTS);
}
@Test
public void testValidTenant_disable() {
// mock 数据
TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.DISABLE.getStatus()));
tenantMapper.insert(tenant);
// 调用,并断言业务异常
assertServiceException(() -> tenantService.validTenant(1L), TENANT_DISABLE, tenant.getName());
}
@Test
public void testValidTenant_expired() {
// mock 数据
TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.ENABLE.getStatus())
.setExpireTime(buildTime(2020, 2, 2)));
tenantMapper.insert(tenant);
// 调用,并断言业务异常
assertServiceException(() -> tenantService.validTenant(1L), TENANT_EXPIRE, tenant.getName());
}
@Test
public void testValidTenant_success() {
// mock 数据
TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.ENABLE.getStatus())
.setExpireTime(LocalDateTime.now().plusDays(1)));
tenantMapper.insert(tenant);
// 调用,并断言业务异常
tenantService.validTenant(1L);
}
@Test
public void testCreateTenant() {
// mock 套餐 100L
TenantPackageDO tenantPackage = randomPojo(TenantPackageDO.class, o -> o.setId(100L));
when(tenantPackageService.validTenantPackage(eq(100L))).thenReturn(tenantPackage);
// mock 角色 200L
when(roleService.createRole(argThat(role -> {
assertEquals(RoleCodeEnum.TENANT_ADMIN.getName(), role.getName());
assertEquals(RoleCodeEnum.TENANT_ADMIN.getCode(), role.getCode());
assertEquals(0, role.getSort());
assertEquals("系统自动生成", role.getRemark());
return true;
}), eq(RoleTypeEnum.SYSTEM.getType()))).thenReturn(200L);
// mock 用户 300L
when(userService.createUser(argThat(user -> {
assertEquals("yunai", user.getUsername());
assertEquals("yuanma", user.getPassword());
assertEquals("灿能", user.getNickname());
assertEquals("15601691300", user.getMobile());
return true;
}))).thenReturn(300L);
// 准备参数
TenantSaveReqVO reqVO = randomPojo(TenantSaveReqVO.class, o -> {
o.setContactName("灿能");
o.setContactMobile("15601691300");
o.setPackageId(100L);
o.setStatus(randomCommonStatus());
o.setWebsites(singletonList("https://www.iocoder.cn"));
o.setUsername("yunai");
o.setPassword("yuanma");
}).setId(null); // 设置为 null方便后面校验
// 调用
Long tenantId = tenantService.createTenant(reqVO);
// 断言
assertNotNull(tenantId);
// 校验记录的属性是否正确
TenantDO tenant = tenantMapper.selectById(tenantId);
assertPojoEquals(reqVO, tenant, "id");
assertEquals(300L, tenant.getContactUserId());
// verify 分配权限
verify(permissionService).assignRoleMenu(eq(200L), same(tenantPackage.getMenuIds()));
// verify 分配角色
verify(permissionService).assignUserRole(eq(300L), eq(singleton(200L)));
}
@Test
public void testUpdateTenant_success() {
// mock 数据
TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setStatus(randomCommonStatus()));
tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
// 准备参数
TenantSaveReqVO reqVO = randomPojo(TenantSaveReqVO.class, o -> {
o.setId(dbTenant.getId()); // 设置更新的 ID
o.setStatus(randomCommonStatus());
o.setWebsites(singletonList(randomString()));
});
// mock 套餐
TenantPackageDO tenantPackage = randomPojo(TenantPackageDO.class,
o -> o.setMenuIds(asSet(200L, 201L)));
when(tenantPackageService.validTenantPackage(eq(reqVO.getPackageId()))).thenReturn(tenantPackage);
// mock 所有角色
RoleDO role100 = randomPojo(RoleDO.class, o -> o.setId(100L).setCode(RoleCodeEnum.TENANT_ADMIN.getCode()));
role100.setTenantId(dbTenant.getId());
RoleDO role101 = randomPojo(RoleDO.class, o -> o.setId(101L));
role101.setTenantId(dbTenant.getId());
when(roleService.getRoleList()).thenReturn(asList(role100, role101));
// mock 每个角色的权限
when(permissionService.getRoleMenuListByRoleId(eq(101L))).thenReturn(asSet(201L, 202L));
// 调用
tenantService.updateTenant(reqVO);
// 校验是否更新正确
TenantDO tenant = tenantMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, tenant);
// verify 设置角色权限
verify(permissionService).assignRoleMenu(eq(100L), eq(asSet(200L, 201L)));
verify(permissionService).assignRoleMenu(eq(101L), eq(asSet(201L)));
}
@Test
public void testUpdateTenant_notExists() {
// 准备参数
TenantSaveReqVO reqVO = randomPojo(TenantSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> tenantService.updateTenant(reqVO), TENANT_NOT_EXISTS);
}
@Test
public void testUpdateTenant_system() {
// mock 数据
TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setPackageId(PACKAGE_ID_SYSTEM));
tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
// 准备参数
TenantSaveReqVO reqVO = randomPojo(TenantSaveReqVO.class, o -> {
o.setId(dbTenant.getId()); // 设置更新的 ID
});
// 调用,校验业务异常
assertServiceException(() -> tenantService.updateTenant(reqVO), TENANT_CAN_NOT_UPDATE_SYSTEM);
}
@Test
public void testDeleteTenant_success() {
// mock 数据
TenantDO dbTenant = randomPojo(TenantDO.class,
o -> o.setStatus(randomCommonStatus()));
tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbTenant.getId();
// 调用
tenantService.deleteTenant(id);
// 校验数据不存在了
assertNull(tenantMapper.selectById(id));
}
@Test
public void testDeleteTenant_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> tenantService.deleteTenant(id), TENANT_NOT_EXISTS);
}
@Test
public void testDeleteTenant_system() {
// mock 数据
TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setPackageId(PACKAGE_ID_SYSTEM));
tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbTenant.getId();
// 调用, 并断言异常
assertServiceException(() -> tenantService.deleteTenant(id), TENANT_CAN_NOT_UPDATE_SYSTEM);
}
@Test
public void testGetTenant() {
// mock 数据
TenantDO dbTenant = randomPojo(TenantDO.class);
tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbTenant.getId();
// 调用
TenantDO result = tenantService.getTenant(id);
// 校验存在
assertPojoEquals(result, dbTenant);
}
@Test
public void testGetTenantPage() {
// mock 数据
TenantDO dbTenant = randomPojo(TenantDO.class, o -> { // 等会查询到
o.setName("灿能源码");
o.setContactName("芋艿");
o.setContactMobile("15601691300");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setCreateTime(buildTime(2020, 12, 12));
});
tenantMapper.insert(dbTenant);
// 测试 name 不匹配
tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setName(randomString())));
// 测试 contactName 不匹配
tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setContactName(randomString())));
// 测试 contactMobile 不匹配
tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setContactMobile(randomString())));
// 测试 status 不匹配
tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 测试 createTime 不匹配
tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setCreateTime(buildTime(2021, 12, 12))));
// 准备参数
TenantPageReqVO reqVO = new TenantPageReqVO();
reqVO.setName("灿能");
reqVO.setContactName("");
reqVO.setContactMobile("1560");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
reqVO.setCreateTime(buildBetweenTime(2020, 12, 1, 2020, 12, 24));
// 调用
PageResult<TenantDO> pageResult = tenantService.getTenantPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbTenant, pageResult.getList().get(0));
}
@Test
public void testGetTenantByName() {
// mock 数据
TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setName("灿能"));
tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
// 调用
TenantDO result = tenantService.getTenantByName("灿能");
// 校验存在
assertPojoEquals(result, dbTenant);
}
@Test
@Disabled // H2 不支持 find_in_set 函数
public void testGetTenantByWebsite() {
// mock 数据
TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setWebsites(singletonList("https://www.iocoder.cn")));
tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
// 调用
TenantDO result = tenantService.getTenantByWebsite("https://www.iocoder.cn");
// 校验存在
assertPojoEquals(result, dbTenant);
}
@Test
public void testGetTenantListByPackageId() {
// mock 数据
TenantDO dbTenant1 = randomPojo(TenantDO.class, o -> o.setPackageId(1L));
tenantMapper.insert(dbTenant1);// @Sql: 先插入出一条存在的数据
TenantDO dbTenant2 = randomPojo(TenantDO.class, o -> o.setPackageId(2L));
tenantMapper.insert(dbTenant2);// @Sql: 先插入出一条存在的数据
// 调用
List<TenantDO> result = tenantService.getTenantListByPackageId(1L);
assertEquals(1, result.size());
assertPojoEquals(dbTenant1, result.get(0));
}
@Test
public void testGetTenantCountByPackageId() {
// mock 数据
TenantDO dbTenant1 = randomPojo(TenantDO.class, o -> o.setPackageId(1L));
tenantMapper.insert(dbTenant1);// @Sql: 先插入出一条存在的数据
TenantDO dbTenant2 = randomPojo(TenantDO.class, o -> o.setPackageId(2L));
tenantMapper.insert(dbTenant2);// @Sql: 先插入出一条存在的数据
// 调用
Long count = tenantService.getTenantCountByPackageId(1L);
assertEquals(1, count);
}
@Test
public void testHandleTenantInfo_disable() {
// 准备参数
TenantInfoHandler handler = mock(TenantInfoHandler.class);
// mock 禁用
when(tenantProperties.getEnable()).thenReturn(false);
// 调用
tenantService.handleTenantInfo(handler);
// 断言
verify(handler, never()).handle(any());
}
@Test
public void testHandleTenantInfo_success() {
// 准备参数
TenantInfoHandler handler = mock(TenantInfoHandler.class);
// mock 未禁用
when(tenantProperties.getEnable()).thenReturn(true);
// mock 租户
TenantDO dbTenant = randomPojo(TenantDO.class);
tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
TenantContextHolder.setTenantId(dbTenant.getId());
// 调用
tenantService.handleTenantInfo(handler);
// 断言
verify(handler).handle(argThat(argument -> {
assertPojoEquals(dbTenant, argument);
return true;
}));
}
@Test
public void testHandleTenantMenu_disable() {
// 准备参数
TenantMenuHandler handler = mock(TenantMenuHandler.class);
// mock 禁用
when(tenantProperties.getEnable()).thenReturn(false);
// 调用
tenantService.handleTenantMenu(handler);
// 断言
verify(handler, never()).handle(any());
}
@Test // 系统租户的情况
public void testHandleTenantMenu_system() {
// 准备参数
TenantMenuHandler handler = mock(TenantMenuHandler.class);
// mock 未禁用
when(tenantProperties.getEnable()).thenReturn(true);
// mock 租户
TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setPackageId(PACKAGE_ID_SYSTEM));
tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
TenantContextHolder.setTenantId(dbTenant.getId());
// mock 菜单
when(menuService.getMenuList()).thenReturn(Arrays.asList(randomPojo(MenuDO.class, o -> o.setId(100L)),
randomPojo(MenuDO.class, o -> o.setId(101L))));
// 调用
tenantService.handleTenantMenu(handler);
// 断言
verify(handler).handle(asSet(100L, 101L));
}
@Test // 普通租户的情况
public void testHandleTenantMenu_normal() {
// 准备参数
TenantMenuHandler handler = mock(TenantMenuHandler.class);
// mock 未禁用
when(tenantProperties.getEnable()).thenReturn(true);
// mock 租户
TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setPackageId(200L));
tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
TenantContextHolder.setTenantId(dbTenant.getId());
// mock 菜单
when(tenantPackageService.getTenantPackage(eq(200L))).thenReturn(randomPojo(TenantPackageDO.class,
o -> o.setMenuIds(asSet(100L, 101L))));
// 调用
tenantService.handleTenantMenu(handler);
// 断言
verify(handler).handle(asSet(100L, 101L));
}
}

View File

@@ -3,7 +3,6 @@ package com.njcn.msgpush.module.system.service.user;
import cn.hutool.core.util.RandomUtil;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.exception.ServiceException;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.collection.ArrayUtils;
import com.njcn.msgpush.framework.common.util.collection.CollectionUtils;
@@ -19,7 +18,6 @@ import com.njcn.msgpush.module.system.controller.admin.user.vo.user.UserSaveReqV
import com.njcn.msgpush.module.system.dal.dataobject.dept.DeptDO;
import com.njcn.msgpush.module.system.dal.dataobject.dept.PostDO;
import com.njcn.msgpush.module.system.dal.dataobject.dept.UserPostDO;
import com.njcn.msgpush.module.system.dal.dataobject.tenant.TenantDO;
import com.njcn.msgpush.module.system.dal.dataobject.user.AdminUserDO;
import com.njcn.msgpush.module.system.dal.mysql.dept.UserPostMapper;
import com.njcn.msgpush.module.system.dal.mysql.user.AdminUserMapper;
@@ -28,7 +26,6 @@ import com.njcn.msgpush.module.system.service.dept.DeptService;
import com.njcn.msgpush.module.system.service.dept.PostService;
import com.njcn.msgpush.module.system.service.oauth2.OAuth2TokenService;
import com.njcn.msgpush.module.system.service.permission.PermissionService;
import com.njcn.msgpush.module.system.service.tenant.TenantService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -80,8 +77,6 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
@MockitoBean
private PasswordEncoder passwordEncoder;
@MockitoBean
private TenantService tenantService;
@MockitoBean
private FileApi fileApi;
@MockitoBean
private ConfigApi configApi;
@@ -102,12 +97,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
o.setMobile(randomString());
o.setPostIds(asSet(1L, 2L));
}).setId(null); // 避免 id 被赋值
// mock 账户额度充足
TenantDO tenant = randomPojo(TenantDO.class, o -> o.setAccountCount(1));
doNothing().when(tenantService).handleTenantInfo(argThat(handler -> {
handler.handle(tenant);
return true;
}));
// mock deptService 的方法
DeptDO dept = randomPojo(DeptDO.class, o -> {
o.setId(reqVO.getDeptId());
@@ -137,20 +127,6 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
assertEquals(2L, userPosts.get(1).getPostId());
}
@Test
public void testCreatUser_max() {
// 准备参数
UserSaveReqVO reqVO = randomPojo(UserSaveReqVO.class);
// mock 账户额度不足
TenantDO tenant = randomPojo(TenantDO.class, o -> o.setAccountCount(-1));
doNothing().when(tenantService).handleTenantInfo(argThat(handler -> {
handler.handle(tenant);
return true;
}));
// 调用,并断言异常
assertServiceException(() -> userService.createUser(reqVO), USER_COUNT_MAX, -1);
}
@Test
public void testUpdateUser_success() {

View File

@@ -1,33 +0,0 @@
DELETE FROM "system_dept";
DELETE FROM "system_dict_data";
DELETE FROM "system_role";
DELETE FROM "system_role_menu";
DELETE FROM "system_menu";
DELETE FROM "system_user_role";
DELETE FROM "system_dict_type";
DELETE FROM "system_user_session";
DELETE FROM "system_post";
DELETE FROM "system_user_post";
DELETE FROM "system_notice";
DELETE FROM "system_login_log";
DELETE FROM "system_operate_log";
DELETE FROM "system_users";
DELETE FROM "system_sms_channel";
DELETE FROM "system_sms_template";
DELETE FROM "system_sms_log";
DELETE FROM "system_sms_code";
DELETE FROM "system_social_client";
DELETE FROM "system_social_user";
DELETE FROM "system_social_user_bind";
DELETE FROM "system_tenant";
DELETE FROM "system_tenant_package";
DELETE FROM "system_oauth2_client";
DELETE FROM "system_oauth2_approve";
DELETE FROM "system_oauth2_access_token";
DELETE FROM "system_oauth2_refresh_token";
DELETE FROM "system_oauth2_code";
DELETE FROM "system_mail_account";
DELETE FROM "system_mail_template";
DELETE FROM "system_mail_log";
DELETE FROM "system_notify_template";
DELETE FROM "system_notify_message";

View File

@@ -1,617 +0,0 @@
CREATE TABLE IF NOT EXISTS "system_dept" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(30) NOT NULL DEFAULT '',
"parent_id" bigint NOT NULL DEFAULT '0',
"sort" int NOT NULL DEFAULT '0',
"leader_user_id" bigint DEFAULT NULL,
"phone" varchar(11) DEFAULT NULL,
"email" varchar(50) DEFAULT NULL,
"status" tinyint NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT '部门表';
CREATE TABLE IF NOT EXISTS "system_dict_data" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"sort" int NOT NULL DEFAULT '0',
"label" varchar(100) NOT NULL DEFAULT '',
"value" varchar(100) NOT NULL DEFAULT '',
"dict_type" varchar(100) NOT NULL DEFAULT '',
"status" tinyint NOT NULL DEFAULT '0',
"color_type" varchar(100) NOT NULL DEFAULT '',
"css_class" varchar(100) NOT NULL DEFAULT '',
"remark" varchar(500) DEFAULT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '字典数据表';
CREATE TABLE IF NOT EXISTS "system_role" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(30) NOT NULL,
"code" varchar(100) NOT NULL,
"sort" int NOT NULL,
"data_scope" tinyint NOT NULL DEFAULT '1',
"data_scope_dept_ids" varchar(500) NOT NULL DEFAULT '',
"status" tinyint NOT NULL,
"type" tinyint NOT NULL,
"remark" varchar(500) DEFAULT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT '角色信息表';
CREATE TABLE IF NOT EXISTS "system_role_menu" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"role_id" bigint NOT NULL,
"menu_id" bigint NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT '角色和菜单关联表';
CREATE TABLE IF NOT EXISTS "system_menu" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(50) NOT NULL,
"permission" varchar(100) NOT NULL DEFAULT '',
"type" tinyint NOT NULL,
"sort" int NOT NULL DEFAULT '0',
"parent_id" bigint NOT NULL DEFAULT '0',
"path" varchar(200) DEFAULT '',
"icon" varchar(100) DEFAULT '#',
"component" varchar(255) DEFAULT NULL,
"component_name" varchar(255) DEFAULT NULL,
"status" tinyint NOT NULL DEFAULT '0',
"visible" bit NOT NULL DEFAULT TRUE,
"keep_alive" bit NOT NULL DEFAULT TRUE,
"always_show" bit NOT NULL DEFAULT TRUE,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '菜单权限表';
CREATE TABLE IF NOT EXISTS "system_user_role" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"role_id" bigint NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp DEFAULT NULL,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp DEFAULT NULL,
"deleted" bit DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT '用户和角色关联表';
CREATE TABLE IF NOT EXISTS "system_dict_type" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(100) NOT NULL DEFAULT '',
"type" varchar(100) NOT NULL DEFAULT '',
"status" tinyint NOT NULL DEFAULT '0',
"remark" varchar(500) DEFAULT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"deleted_time" timestamp NOT NULL,
PRIMARY KEY ("id")
) COMMENT '字典类型表';
CREATE TABLE IF NOT EXISTS `system_user_session` (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`token` varchar(32) NOT NULL,
`user_id` bigint DEFAULT NULL,
"user_type" tinyint NOT NULL,
`username` varchar(50) NOT NULL DEFAULT '',
`user_ip` varchar(50) DEFAULT NULL,
`user_agent` varchar(512) DEFAULT NULL,
`session_timeout` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` varchar(64) DEFAULT '' ,
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY (`id`)
) COMMENT '用户在线 Session';
CREATE TABLE IF NOT EXISTS "system_post" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"code" varchar(64) NOT NULL,
"name" varchar(50) NOT NULL,
"sort" integer NOT NULL,
"status" tinyint NOT NULL,
"remark" varchar(500) DEFAULT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT '岗位信息表';
CREATE TABLE IF NOT EXISTS `system_user_post`(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint DEFAULT NULL,
"post_id" bigint DEFAULT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY (`id`)
) COMMENT ='用户岗位表';
CREATE TABLE IF NOT EXISTS "system_notice" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"title" varchar(50) NOT NULL COMMENT '公告标题',
"content" text NOT NULL COMMENT '公告内容',
"type" tinyint NOT NULL COMMENT '公告类型1通知 2公告',
"status" tinyint NOT NULL DEFAULT '0' COMMENT '公告状态0正常 1关闭',
"creator" varchar(64) DEFAULT '' COMMENT '创建者',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
"updater" varchar(64) DEFAULT '' COMMENT '更新者',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
"deleted" bit NOT NULL DEFAULT 0 COMMENT '是否删除',
"tenant_id" bigint not null default '0',
PRIMARY KEY("id")
) COMMENT '通知公告表';
CREATE TABLE IF NOT EXISTS `system_login_log` (
`id` bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`log_type` bigint(4) NOT NULL,
"user_id" bigint not null default '0',
"user_type" tinyint NOT NULL,
`trace_id` varchar(64) NOT NULL DEFAULT '',
`username` varchar(50) NOT NULL DEFAULT '',
`result` tinyint(4) NOT NULL,
`user_ip` varchar(50) NOT NULL,
`user_agent` varchar(512) NOT NULL,
`creator` varchar(64) DEFAULT '',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` varchar(64) DEFAULT '',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) COMMENT ='系统访问记录';
CREATE TABLE IF NOT EXISTS `system_operate_log` (
`id` bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`trace_id` varchar(64) NOT NULL DEFAULT '',
`user_id` bigint(20) NOT NULL,
"user_type" tinyint not null default '0',
`type` varchar(50) NOT NULL,
`sub_type` varchar(50) NOT NULL,
`biz_id` bigint(20) NOT NULL,
`action` varchar(2000) NOT NULL DEFAULT '',
`extra` varchar(512) NOT NULL DEFAULT '',
`request_method` varchar(16) DEFAULT '',
`request_url` varchar(255) DEFAULT '',
`user_ip` varchar(50) DEFAULT NULL,
`user_agent` varchar(200) DEFAULT NULL,
`creator` varchar(64) DEFAULT '',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` varchar(64) DEFAULT '',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) NOT NULL DEFAULT '0',
"tenant_id" bigint not null default '0',
PRIMARY KEY (`id`)
) COMMENT ='操作日志记录';
CREATE TABLE IF NOT EXISTS "system_users" (
"id" bigint not null GENERATED BY DEFAULT AS IDENTITY,
"username" varchar(30) not null,
"password" varchar(100) not null default '',
"nickname" varchar(30) not null,
"remark" varchar(500) default null,
"dept_id" bigint default null,
"post_ids" varchar(255) default null,
"email" varchar(50) default '',
"mobile" varchar(11) default '',
"sex" tinyint default '0',
"avatar" varchar(100) default '',
"status" tinyint not null default '0',
"login_ip" varchar(50) default '',
"login_date" timestamp default null,
"creator" varchar(64) default '',
"create_time" timestamp not null default current_timestamp,
"updater" varchar(64) default '',
"update_time" timestamp not null default current_timestamp,
"deleted" bit not null default false,
"tenant_id" bigint not null default '0',
primary key ("id")
) comment '用户信息表';
CREATE TABLE IF NOT EXISTS "system_sms_channel" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"signature" varchar(10) NOT NULL,
"code" varchar(63) NOT NULL,
"status" tinyint NOT NULL,
"remark" varchar(255) DEFAULT NULL,
"api_key" varchar(63) NOT NULL,
"api_secret" varchar(63) DEFAULT NULL,
"callback_url" varchar(255) DEFAULT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '短信渠道';
CREATE TABLE IF NOT EXISTS "system_sms_template" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"type" tinyint NOT NULL,
"status" tinyint NOT NULL,
"code" varchar(63) NOT NULL,
"name" varchar(63) NOT NULL,
"content" varchar(255) NOT NULL,
"params" varchar(255) NOT NULL,
"remark" varchar(255) DEFAULT NULL,
"api_template_id" varchar(63) NOT NULL,
"channel_id" bigint NOT NULL,
"channel_code" varchar(63) NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '短信模板';
CREATE TABLE IF NOT EXISTS "system_sms_log" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"channel_id" bigint NOT NULL,
"channel_code" varchar(63) NOT NULL,
"template_id" bigint NOT NULL,
"template_code" varchar(63) NOT NULL,
"template_type" tinyint NOT NULL,
"template_content" varchar(255) NOT NULL,
"template_params" varchar(255) NOT NULL,
"api_template_id" varchar(63) NOT NULL,
"mobile" varchar(11) NOT NULL,
"user_id" bigint DEFAULT '0',
"user_type" tinyint DEFAULT '0',
"send_status" tinyint NOT NULL DEFAULT '0',
"send_time" timestamp DEFAULT NULL,
"send_code" int DEFAULT NULL,
"send_msg" varchar(255) DEFAULT NULL,
"api_send_code" varchar(63) DEFAULT NULL,
"api_send_msg" varchar(255) DEFAULT NULL,
"api_request_id" varchar(255) DEFAULT NULL,
"api_serial_no" varchar(255) DEFAULT NULL,
"receive_status" tinyint NOT NULL DEFAULT '0',
"receive_time" timestamp DEFAULT NULL,
"api_receive_code" varchar(63) DEFAULT NULL,
"api_receive_msg" varchar(255) DEFAULT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '短信日志';
CREATE TABLE IF NOT EXISTS "system_sms_code" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"mobile" varchar(11) NOT NULL,
"code" varchar(11) NOT NULL,
"scene" bigint NOT NULL,
"create_ip" varchar NOT NULL,
"today_index" int NOT NULL,
"used" bit NOT NULL DEFAULT FALSE,
"used_time" timestamp DEFAULT NULL,
"used_ip" varchar NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '短信日志';
CREATE TABLE IF NOT EXISTS "system_social_client" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(255) NOT NULL,
"social_type" int NOT NULL,
"user_type" int NOT NULL,
"client_id" varchar(255) NOT NULL,
"client_secret" varchar(255) NOT NULL,
"public_key" varchar(2048) NOT NULL,
"agent_id" varchar(255) NOT NULL,
"status" int NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT '社交客户端表';
CREATE TABLE IF NOT EXISTS "system_social_user" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"type" tinyint NOT NULL,
"openid" varchar(64) NOT NULL,
"token" varchar(256) DEFAULT NULL,
"raw_token_info" varchar(1024) NOT NULL,
"nickname" varchar(32) NOT NULL,
"avatar" varchar(255) DEFAULT NULL,
"raw_user_info" varchar(1024) NOT NULL,
"code" varchar(64) NOT NULL,
"state" varchar(64),
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '社交用户';
CREATE TABLE IF NOT EXISTS "system_social_user_bind" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"user_type" tinyint NOT NULL,
"social_type" tinyint NOT NULL,
"social_user_id" number NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '社交用户的绑定';
CREATE TABLE IF NOT EXISTS "system_tenant" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(63) NOT NULL,
"contact_user_id" bigint NOT NULL DEFAULT '0',
"contact_name" varchar(255) NOT NULL,
"contact_mobile" varchar(255),
"status" tinyint NOT NULL,
"websites" varchar(1024) DEFAULT '',
"package_id" bigint NOT NULL,
"expire_time" timestamp NOT NULL,
"account_count" int NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '租户';
CREATE TABLE IF NOT EXISTS "system_tenant_package" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(30) NOT NULL,
"status" tinyint NOT NULL,
"remark" varchar(256),
"menu_ids" varchar(2048) NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '租户套餐表';
CREATE TABLE IF NOT EXISTS "system_oauth2_client" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"client_id" varchar NOT NULL,
"secret" varchar NOT NULL,
"name" varchar NOT NULL,
"logo" varchar NOT NULL,
"description" varchar,
"status" int NOT NULL,
"access_token_validity_seconds" int NOT NULL,
"refresh_token_validity_seconds" int NOT NULL,
"redirect_uris" varchar NOT NULL,
"authorized_grant_types" varchar NOT NULL,
"scopes" varchar NOT NULL DEFAULT '',
"auto_approve_scopes" varchar NOT NULL DEFAULT '',
"authorities" varchar NOT NULL DEFAULT '',
"resource_ids" varchar NOT NULL DEFAULT '',
"additional_information" varchar NOT NULL DEFAULT '',
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT 'OAuth2 客户端表';
CREATE TABLE IF NOT EXISTS "system_oauth2_approve" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"user_type" tinyint NOT NULL,
"client_id" varchar NOT NULL,
"scope" varchar NOT NULL,
"approved" bit NOT NULL DEFAULT FALSE,
"expires_time" datetime NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT 'OAuth2 批准表';
CREATE TABLE IF NOT EXISTS "system_oauth2_access_token" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"user_type" tinyint NOT NULL,
"user_info" varchar NOT NULL,
"access_token" varchar NOT NULL,
"refresh_token" varchar NOT NULL,
"client_id" varchar NOT NULL,
"scopes" varchar NOT NULL,
"approved" bit NOT NULL DEFAULT FALSE,
"expires_time" datetime NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null,
PRIMARY KEY ("id")
) COMMENT 'OAuth2 访问令牌';
CREATE TABLE IF NOT EXISTS "system_oauth2_refresh_token" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"user_type" tinyint NOT NULL,
"refresh_token" varchar NOT NULL,
"client_id" varchar NOT NULL,
"scopes" varchar NOT NULL,
"approved" bit NOT NULL DEFAULT FALSE,
"expires_time" datetime NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT 'OAuth2 刷新令牌';
CREATE TABLE IF NOT EXISTS "system_oauth2_code" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"user_type" tinyint NOT NULL,
"code" varchar NOT NULL,
"client_id" varchar NOT NULL,
"scopes" varchar NOT NULL,
"expires_time" datetime NOT NULL,
"redirect_uri" varchar NOT NULL,
"state" varchar NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT 'OAuth2 刷新令牌';
CREATE TABLE IF NOT EXISTS "system_mail_account" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"mail" varchar NOT NULL,
"username" varchar NOT NULL,
"password" varchar NOT NULL,
"host" varchar NOT NULL,
"port" int NOT NULL,
"ssl_enable" bit NOT NULL,
"starttls_enable" bit NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '邮箱账号表';
CREATE TABLE IF NOT EXISTS "system_mail_template" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL,
"code" varchar NOT NULL,
"account_id" bigint NOT NULL,
"nickname" varchar,
"title" varchar NOT NULL,
"content" varchar NOT NULL,
"params" varchar NOT NULL,
"status" varchar NOT NULL,
"remark" varchar,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '邮件模版表';
CREATE TABLE IF NOT EXISTS "system_mail_log" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint,
"user_type" varchar,
"to_mails" varchar NOT NULL,
"cc_mails" varchar,
"bcc_mails" varchar,
"account_id" bigint NOT NULL,
"from_mail" varchar NOT NULL,
"template_id" bigint NOT NULL,
"template_code" varchar NOT NULL,
"template_nickname" varchar,
"template_title" varchar NOT NULL,
"template_content" varchar NOT NULL,
"template_params" varchar NOT NULL,
"send_status" varchar NOT NULL,
"send_time" datetime,
"send_message_id" varchar,
"send_exception" varchar,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '邮件日志表';
CREATE TABLE IF NOT EXISTS "system_notify_template" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL,
"code" varchar NOT NULL,
"nickname" varchar NOT NULL,
"content" varchar NOT NULL,
"type" varchar NOT NULL,
"params" varchar,
"status" varchar NOT NULL,
"remark" varchar,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '站内信模板表';
CREATE TABLE IF NOT EXISTS "system_notify_message" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"user_type" varchar NOT NULL,
"template_id" bigint NOT NULL,
"template_code" varchar NOT NULL,
"template_nickname" varchar NOT NULL,
"template_content" varchar NOT NULL,
"template_type" int NOT NULL,
"template_params" varchar NOT NULL,
"read_status" bit NOT NULL,
"read_time" varchar,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT '站内信消息表';