feat(tenant): 租户品牌定制(brand_name/logo_url/theme_color/copyright + GET /auth/brand)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
liuyun 2026-10-11 22:32:14 +08:00
parent bad8639175
commit f747ad5442
6 changed files with 88 additions and 8 deletions

View File

@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE `sys_tenant` ADD COLUMN `brand_name` VARCHAR(64) NULL,
ADD COLUMN `copyright` VARCHAR(255) NULL,
ADD COLUMN `logo_url` VARCHAR(255) NULL,
ADD COLUMN `theme_color` VARCHAR(16) NULL;

View File

@ -12,14 +12,18 @@ datasource db {
/// 租户
model Tenant {
id String @id @default(uuid(7)) @db.VarChar(36)
code String @unique @db.VarChar(64)
name String @db.VarChar(128)
status String @default("enabled") @db.VarChar(16)
remark String? @db.VarChar(255)
createdAt DateTime @default(now()) @map("created_at")
createdBy String? @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
id String @id @default(uuid(7)) @db.VarChar(36)
code String @unique @db.VarChar(64)
name String @db.VarChar(128)
status String @default("enabled") @db.VarChar(16)
remark String? @db.VarChar(255)
brandName String? @map("brand_name") @db.VarChar(64)
logoUrl String? @map("logo_url") @db.VarChar(255)
themeColor String? @map("theme_color") @db.VarChar(16)
copyright String? @db.VarChar(255)
createdAt DateTime @default(now()) @map("created_at")
createdBy String? @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
@@map("sys_tenant")
}

View File

@ -49,6 +49,14 @@ export class AuthController {
return this.auth.getPermissionSet(ctx.tenantCode, ctx.userId);
}
/** 当前租户品牌(仅需登录)。 */
@Get('brand')
brand() {
const ctx = getRequestContext();
if (!ctx) throw new UnauthorizedException('未登录');
return this.auth.getBrand(ctx.tenantCode);
}
/** 修改本人密码(仅需登录)。 */
@Patch('me/password')
changePassword(@Body() dto: ChangePasswordDto) {

View File

@ -439,6 +439,25 @@ export class AuthService {
return (await this.redis.getJson(sessionRevokedKey(sid))) != null;
}
/** 当前租户品牌(登录后前端拉取应用)。 */
async getBrand(tenantCode: string): Promise<{
name: string;
brandName: string | null;
logoUrl: string | null;
themeColor: string | null;
copyright: string | null;
}> {
const tenant = await this.prisma.client.tenant.findUnique({ where: { code: tenantCode } });
if (!tenant) throw new UnauthorizedException('租户不存在');
return {
name: tenant.name,
brandName: tenant.brandName,
logoUrl: tenant.logoUrl,
themeColor: tenant.themeColor,
copyright: tenant.copyright,
};
}
/** 聚合权限码与超管标志。 */
private async resolveAuth(tenantCode: string, userId: string): Promise<PermissionSet> {
const roleRows = await this.prisma.client.userRole.findMany({

View File

@ -33,6 +33,26 @@ export class CreateTenantDto {
@IsOptional()
@IsString()
remark?: string;
@ApiPropertyOptional({ description: '品牌名' })
@IsOptional()
@IsString()
brandName?: string;
@ApiPropertyOptional({ description: 'Logo 地址' })
@IsOptional()
@IsString()
logoUrl?: string;
@ApiPropertyOptional({ description: '主色(如 #2E7FA3)' })
@IsOptional()
@IsString()
themeColor?: string;
@ApiPropertyOptional({ description: '版权文案' })
@IsOptional()
@IsString()
copyright?: string;
}
export class UpdateTenantDto {
@ -50,6 +70,26 @@ export class UpdateTenantDto {
@IsOptional()
@IsString()
remark?: string;
@ApiPropertyOptional({ description: '品牌名' })
@IsOptional()
@IsString()
brandName?: string;
@ApiPropertyOptional({ description: 'Logo 地址' })
@IsOptional()
@IsString()
logoUrl?: string;
@ApiPropertyOptional({ description: '主色(如 #2E7FA3)' })
@IsOptional()
@IsString()
themeColor?: string;
@ApiPropertyOptional({ description: '版权文案' })
@IsOptional()
@IsString()
copyright?: string;
}
export class ListTenantQuery extends PageQuery {

View File

@ -30,6 +30,10 @@ export class TenantsService extends BaseCrudService {
name: dto.name,
status: dto.status ?? 'enabled',
remark: dto.remark,
brandName: dto.brandName,
logoUrl: dto.logoUrl,
themeColor: dto.themeColor,
copyright: dto.copyright,
createdBy: ctx?.userId ?? null,
},
});