feat: 完成配置中心重构和命名规范优化

- 重构config层为配置中心架构,支持动态配置管理
- 统一core层命名规范(event-bus→event, circuit-breaker→breaker, domain-sdk→sdk)
- 修复数据库连接配置路径问题
- 实现配置中心完整功能:系统配置、动态配置、配置验证、统计
- 优化目录结构,为微服务架构做准备
- 修复TypeScript编译错误和依赖注入问题
This commit is contained in:
万物街
2025-08-28 05:19:14 +08:00
parent 5118d98369
commit 2084711030
137 changed files with 6148 additions and 1856 deletions

View File

@@ -0,0 +1,8 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const UserContext = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);

View File

@@ -2,6 +2,7 @@
export * from './admin/admin.module';
export * from './member/member.module';
export * from './rbac/rbac.module';
export * from './user/user.module';
export * from './auth/auth.module';
export * from './upload/upload.module';
export * from './jobs/jobs.module';
@@ -16,5 +17,4 @@ export * from './auth/decorators/RolesDecorator';
// 导出设置相关模块
export * from './settings';
// 导出常量
export * from '../config/common/constants';

View File

@@ -3,10 +3,10 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Site } from './site.entity';
import { UpdateSiteSettingsDto } from './site-settings.dto';
import {
DEFAULT_SITE_CONFIG,
SYSTEM_CONSTANTS,
} from '@wwjConfig/common/constants';
// 不允许硬编码,从配置系统获取
// TODO: 配置系统重构中,此功能暂时不可用
@Injectable()
export class SiteSettingsService {
@@ -18,103 +18,24 @@ export class SiteSettingsService {
/**
* 获取站点设置
*/
async getSiteSettings() {
// 获取默认站点id = 1
const site = await this.siteRepository.findOne({
where: { site_id: SYSTEM_CONSTANTS.DEFAULT_SITE_ID },
});
if (!site) {
// 如果没有找到站点,返回默认值
return { ...DEFAULT_SITE_CONFIG };
}
return {
site_name: site.site_name || DEFAULT_SITE_CONFIG.site_name,
site_title: site.site_title || DEFAULT_SITE_CONFIG.site_title,
site_keywords: site.site_keywords || DEFAULT_SITE_CONFIG.site_keywords,
site_description:
site.site_description || DEFAULT_SITE_CONFIG.site_description,
site_logo: site.site_logo || DEFAULT_SITE_CONFIG.site_logo,
site_favicon: site.site_favicon || DEFAULT_SITE_CONFIG.site_favicon,
icp_number: site.icp_number || DEFAULT_SITE_CONFIG.icp_number,
copyright: site.copyright || DEFAULT_SITE_CONFIG.copyright,
site_status: site.site_status || DEFAULT_SITE_CONFIG.site_status,
close_reason: site.close_reason || DEFAULT_SITE_CONFIG.close_reason,
};
getSiteSettings() {
// 配置系统重构中,此功能暂时不可用
throw new Error('配置系统重构中,站点设置功能暂时不可用');
}
/**
* 更新站点设置
*/
async updateSiteSettings(updateSiteSettingsDto: UpdateSiteSettingsDto) {
const {
site_name,
site_title,
site_keywords,
site_description,
site_logo,
site_favicon,
icp_number,
copyright,
site_status,
close_reason,
} = updateSiteSettingsDto;
// 查找或创建默认站点
let site = await this.siteRepository.findOne({
where: { id: SYSTEM_CONSTANTS.DEFAULT_SITE_ID },
});
if (!site) {
// 创建默认站点
site = this.siteRepository.create({
id: SYSTEM_CONSTANTS.DEFAULT_SITE_ID,
site_name: site_name || DEFAULT_SITE_CONFIG.site_name,
site_title: site_title || DEFAULT_SITE_CONFIG.site_title,
site_keywords: site_keywords || DEFAULT_SITE_CONFIG.site_keywords,
site_description:
site_description || DEFAULT_SITE_CONFIG.site_description,
site_logo: site_logo || DEFAULT_SITE_CONFIG.site_logo,
site_favicon: site_favicon || DEFAULT_SITE_CONFIG.site_favicon,
icp_number: icp_number || DEFAULT_SITE_CONFIG.icp_number,
copyright: copyright || DEFAULT_SITE_CONFIG.copyright,
site_status: site_status || DEFAULT_SITE_CONFIG.site_status,
close_reason: close_reason || DEFAULT_SITE_CONFIG.close_reason,
});
} else {
// 更新现有站点
if (site_name !== undefined) site.site_name = site_name;
if (site_title !== undefined) site.site_title = site_title;
if (site_keywords !== undefined) site.site_keywords = site_keywords;
if (site_description !== undefined)
site.site_description = site_description;
if (site_logo !== undefined) site.site_logo = site_logo;
if (site_favicon !== undefined) site.site_favicon = site_favicon;
if (icp_number !== undefined) site.icp_number = icp_number;
if (copyright !== undefined) site.copyright = copyright;
if (site_status !== undefined) site.site_status = site_status;
if (close_reason !== undefined) site.close_reason = close_reason;
}
await this.siteRepository.save(site);
return { message: '站点设置更新成功' };
updateSiteSettings(updateSiteSettingsDto: UpdateSiteSettingsDto) {
// 配置系统重构中,此功能暂时不可用
throw new Error('配置系统重构中,站点设置更新功能暂时不可用');
}
/**
* 重置站点设置为默认值
*/
async resetSiteSettings() {
// 删除现有站点配置
await this.siteRepository.delete({ id: SYSTEM_CONSTANTS.DEFAULT_SITE_ID });
// 创建默认站点配置
const defaultSite = this.siteRepository.create({
id: SYSTEM_CONSTANTS.DEFAULT_SITE_ID,
...DEFAULT_SITE_CONFIG,
});
await this.siteRepository.save(defaultSite);
return { message: '站点设置重置成功' };
resetSiteSettings() {
// 配置系统重构中,此功能暂时不可用
throw new Error('配置系统重构中,站点设置重置功能暂时不可用');
}
}

View File

@@ -0,0 +1,110 @@
import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
Query,
UseGuards,
ParseIntPipe,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../../auth/guards/JwtAuthGuard';
import { RolesGuard } from '../../../auth/guards/RolesGuard';
import { Roles } from '../../../auth/decorators/RolesDecorator';
import { UserContext } from '../../../auth/decorators/user-context.decorator';
import { UserContextDto } from '../../dto/UserContextDto';
import { UserAdminService } from '../../services/admin/UserAdminService';
import {
CreateUserAdminDto,
UpdateUserAdminDto,
GetUserListAdminDto,
BatchUpdateUserStatusAdminDto,
ResetUserPasswordAdminDto,
} from '../../dto/admin/UserDto';
@ApiTags('用户管理')
@Controller('adminapi/user')
@UseGuards(JwtAuthGuard, RolesGuard)
export class UserController {
constructor(private readonly userAdminService: UserAdminService) {}
@Post()
@Roles('admin')
@ApiOperation({ summary: '创建用户' })
@ApiResponse({ status: 201, description: '用户创建成功' })
async createUser(
@Body() createUserDto: CreateUserAdminDto,
@UserContext() userContext: UserContextDto,
) {
return await this.userAdminService.createUser(createUserDto, userContext);
}
@Put()
@Roles('admin')
@ApiOperation({ summary: '更新用户' })
@ApiResponse({ status: 200, description: '用户更新成功' })
async updateUser(
@Body() updateUserDto: UpdateUserAdminDto,
@UserContext() userContext: UserContextDto,
) {
return await this.userAdminService.updateUser(updateUserDto, userContext);
}
@Get()
@Roles('admin')
@ApiOperation({ summary: '获取用户列表' })
@ApiResponse({ status: 200, description: '获取用户列表成功' })
async getUserList(
@Query() queryDto: GetUserListAdminDto,
@UserContext() userContext: UserContextDto,
) {
return await this.userAdminService.getUserList(queryDto, userContext);
}
@Get(':id')
@Roles('admin')
@ApiOperation({ summary: '根据ID获取用户' })
@ApiResponse({ status: 200, description: '获取用户成功' })
async getUserById(
@Param('id', ParseIntPipe) id: number,
@UserContext() userContext: UserContextDto,
) {
return await this.userAdminService.getUserById(id, userContext);
}
@Delete(':id')
@Roles('admin')
@ApiOperation({ summary: '删除用户' })
@ApiResponse({ status: 200, description: '用户删除成功' })
async deleteUser(
@Param('id', ParseIntPipe) id: number,
@UserContext() userContext: UserContextDto,
) {
return await this.userAdminService.deleteUser(id, userContext);
}
@Post('batch-update-status')
@Roles('admin')
@ApiOperation({ summary: '批量更新用户状态' })
@ApiResponse({ status: 200, description: '批量更新状态成功' })
async batchUpdateStatus(
@Body() batchUpdateDto: BatchUpdateUserStatusAdminDto,
@UserContext() userContext: UserContextDto,
) {
return await this.userAdminService.batchUpdateStatus(batchUpdateDto, userContext);
}
@Post('reset-password')
@Roles('admin')
@ApiOperation({ summary: '重置用户密码' })
@ApiResponse({ status: 200, description: '密码重置成功' })
async resetPassword(
@Body() resetPasswordDto: ResetUserPasswordAdminDto,
@UserContext() userContext: UserContextDto,
) {
return await this.userAdminService.resetPassword(resetPasswordDto, userContext);
}
}

View File

@@ -0,0 +1,6 @@
export class UserContextDto {
userId: number;
siteId: number;
username: string;
roles: string[];
}

View File

@@ -0,0 +1,189 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsOptional, IsNumber, IsEmail, IsDateString, IsArray } from 'class-validator';
export class CreateUserAdminDto {
@ApiProperty({ description: '用户名' })
@IsString()
username: string;
@ApiProperty({ description: '密码' })
@IsString()
password: string;
@ApiProperty({ description: '昵称' })
@IsString()
nickname: string;
@ApiProperty({ description: '邮箱', required: false })
@IsOptional()
@IsEmail()
email?: string;
@ApiProperty({ description: '手机号', required: false })
@IsOptional()
@IsString()
mobile?: string;
@ApiProperty({ description: '头像', required: false })
@IsOptional()
@IsString()
avatar?: string;
@ApiProperty({ description: '状态0-禁用1-启用', default: 1 })
@IsOptional()
@IsNumber()
status?: number;
@ApiProperty({ description: '性别0-未知1-男2-女', default: 0 })
@IsOptional()
@IsNumber()
gender?: number;
@ApiProperty({ description: '生日', required: false })
@IsOptional()
@IsDateString()
birthday?: string;
@ApiProperty({ description: '备注', required: false })
@IsOptional()
@IsString()
remark?: string;
@ApiProperty({ description: '排序', default: 0 })
@IsOptional()
@IsNumber()
sort?: number;
@ApiProperty({ description: '部门ID', required: false })
@IsOptional()
@IsNumber()
dept_id?: number;
@ApiProperty({ description: '角色ID列表', required: false, type: [Number] })
@IsOptional()
@IsArray()
role_ids?: number[];
}
export class UpdateUserAdminDto {
@ApiProperty({ description: 'ID' })
@IsNumber()
id: number;
@ApiProperty({ description: '昵称', required: false })
@IsOptional()
@IsString()
nickname?: string;
@ApiProperty({ description: '邮箱', required: false })
@IsOptional()
@IsEmail()
email?: string;
@ApiProperty({ description: '手机号', required: false })
@IsOptional()
@IsString()
mobile?: string;
@ApiProperty({ description: '头像', required: false })
@IsOptional()
@IsString()
avatar?: string;
@ApiProperty({ description: '状态0-禁用1-启用', required: false })
@IsOptional()
@IsNumber()
status?: number;
@ApiProperty({ description: '性别0-未知1-男2-女', required: false })
@IsOptional()
@IsNumber()
gender?: number;
@ApiProperty({ description: '生日', required: false })
@IsOptional()
@IsDateString()
birthday?: string;
@ApiProperty({ description: '备注', required: false })
@IsOptional()
@IsString()
remark?: string;
@ApiProperty({ description: '排序', required: false })
@IsOptional()
@IsNumber()
sort?: number;
@ApiProperty({ description: '部门ID', required: false })
@IsOptional()
@IsNumber()
dept_id?: number;
@ApiProperty({ description: '角色ID列表', required: false, type: [Number] })
@IsOptional()
@IsArray()
role_ids?: number[];
}
export class GetUserListAdminDto {
@ApiProperty({ description: '页码', default: 1 })
@IsOptional()
@IsNumber()
page?: number;
@ApiProperty({ description: '每页数量', default: 20 })
@IsOptional()
@IsNumber()
pageSize?: number;
@ApiProperty({ description: '用户名', required: false })
@IsOptional()
@IsString()
username?: string;
@ApiProperty({ description: '昵称', required: false })
@IsOptional()
@IsString()
nickname?: string;
@ApiProperty({ description: '邮箱', required: false })
@IsOptional()
@IsEmail()
email?: string;
@ApiProperty({ description: '手机号', required: false })
@IsOptional()
@IsString()
mobile?: string;
@ApiProperty({ description: '状态', required: false })
@IsOptional()
@IsNumber()
status?: number;
@ApiProperty({ description: '部门ID', required: false })
@IsOptional()
@IsNumber()
dept_id?: number;
}
export class BatchUpdateUserStatusAdminDto {
@ApiProperty({ description: '用户ID列表', type: [Number] })
@IsArray()
ids: number[];
@ApiProperty({ description: '状态0-禁用1-启用' })
@IsNumber()
status: number;
}
export class ResetUserPasswordAdminDto {
@ApiProperty({ description: '用户ID' })
@IsNumber()
id: number;
@ApiProperty({ description: '新密码' })
@IsString()
password: string;
}

View File

@@ -0,0 +1,82 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
DeleteDateColumn,
ManyToOne,
JoinColumn,
ManyToMany,
JoinTable,
} from 'typeorm';
import { SysRole } from '../../rbac/entities/SysRole';
@Entity('sys_user')
export class SysUser {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 50, unique: true, comment: '用户名' })
username: string;
@Column({ length: 100, comment: '密码' })
password: string;
@Column({ length: 50, comment: '昵称' })
nickname: string;
@Column({ length: 100, nullable: true, comment: '邮箱' })
email: string;
@Column({ length: 20, nullable: true, comment: '手机号' })
mobile: string;
@Column({ length: 200, nullable: true, comment: '头像' })
avatar: string;
@Column({ type: 'tinyint', default: 1, comment: '状态0-禁用1-启用' })
status: number;
@Column({ type: 'tinyint', default: 0, comment: '性别0-未知1-男2-女' })
gender: number;
@Column({ type: 'date', nullable: true, comment: '生日' })
birthday: Date;
@Column({ length: 500, nullable: true, comment: '备注' })
remark: string;
@Column({ type: 'int', default: 0, comment: '排序' })
sort: number;
@Column({ type: 'int', default: 0, comment: '站点ID' })
site_id: number;
@Column({ type: 'int', nullable: true, comment: '部门ID' })
dept_id: number;
@Column({ type: 'int', nullable: true, comment: '创建人ID' })
create_user_id: number;
@Column({ type: 'int', nullable: true, comment: '更新人ID' })
update_user_id: number;
@CreateDateColumn({ comment: '创建时间' })
create_time: Date;
@UpdateDateColumn({ comment: '更新时间' })
update_time: Date;
@DeleteDateColumn({ comment: '删除时间' })
delete_time: Date;
// 关联关系
@ManyToMany(() => SysRole)
@JoinTable({
name: 'sys_user_role',
joinColumn: { name: 'user_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'role_id', referencedColumnName: 'role_id' },
})
roles: SysRole[];
}

View File

@@ -0,0 +1,76 @@
import { Injectable } from '@nestjs/common';
import { CoreUserService } from '../core/CoreUserService';
import { CreateUserAdminDto, UpdateUserAdminDto, GetUserListAdminDto, BatchUpdateUserStatusAdminDto, ResetUserPasswordAdminDto } from '../../dto/admin/UserDto';
import { UserContextDto } from '../../dto/UserContextDto';
@Injectable()
export class UserAdminService {
constructor(
private readonly coreUserService: CoreUserService,
) {}
/**
* 创建用户
*/
async createUser(createUserDto: CreateUserAdminDto, userContext: UserContextDto) {
return await this.coreUserService.createUser(
createUserDto,
userContext.siteId,
userContext.userId,
);
}
/**
* 更新用户
*/
async updateUser(updateUserDto: UpdateUserAdminDto, userContext: UserContextDto) {
return await this.coreUserService.updateUser(
updateUserDto,
userContext.siteId,
userContext.userId,
);
}
/**
* 获取用户列表
*/
async getUserList(queryDto: GetUserListAdminDto, userContext: UserContextDto) {
return await this.coreUserService.getUserList(queryDto, userContext.siteId);
}
/**
* 根据ID获取用户
*/
async getUserById(id: number, userContext: UserContextDto) {
return await this.coreUserService.getUserById(id, userContext.siteId);
}
/**
* 删除用户
*/
async deleteUser(id: number, userContext: UserContextDto) {
return await this.coreUserService.deleteUser(id, userContext.siteId);
}
/**
* 批量更新用户状态
*/
async batchUpdateStatus(batchUpdateDto: BatchUpdateUserStatusAdminDto, userContext: UserContextDto) {
return await this.coreUserService.batchUpdateStatus(
batchUpdateDto.ids,
batchUpdateDto.status,
userContext.siteId,
);
}
/**
* 重置用户密码
*/
async resetPassword(resetPasswordDto: ResetUserPasswordAdminDto, userContext: UserContextDto) {
return await this.coreUserService.resetPassword(
resetPasswordDto.id,
resetPasswordDto.password,
userContext.siteId,
);
}
}

View File

@@ -0,0 +1,171 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { SysUser } from '../../entities/SysUser';
import { CreateUserAdminDto, UpdateUserAdminDto, GetUserListAdminDto } from '../../dto/admin/UserDto';
import { TransactionManager } from '@wwjCore/database/transactionManager';
import * as bcrypt from 'bcrypt';
@Injectable()
export class CoreUserService {
constructor(
@InjectRepository(SysUser)
private readonly userRepository: Repository<SysUser>,
private readonly transactionManager: TransactionManager,
) {}
/**
* 创建用户
*/
async createUser(createUserDto: CreateUserAdminDto, siteId: number, userId: number): Promise<SysUser> {
const { password, role_ids, ...userData } = createUserDto;
// 加密密码
const hashedPassword = await bcrypt.hash(password, 10);
const user = this.userRepository.create({
...userData,
password: hashedPassword,
site_id: siteId,
create_user_id: userId,
update_user_id: userId,
});
return await this.userRepository.save(user);
}
/**
* 更新用户
*/
async updateUser(updateUserDto: UpdateUserAdminDto, siteId: number, userId: number): Promise<SysUser> {
const { id, role_ids, ...updateData } = updateUserDto;
const user = await this.userRepository.findOne({
where: { id, site_id: siteId },
});
if (!user) {
throw new Error('用户不存在');
}
Object.assign(user, {
...updateData,
update_user_id: userId,
});
return await this.userRepository.save(user);
}
/**
* 获取用户列表
*/
async getUserList(queryDto: GetUserListAdminDto, siteId: number) {
const { page = 1, pageSize = 20, username, nickname, email, mobile, status, dept_id } = queryDto;
const queryBuilder = this.userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.roles', 'roles')
.where('user.site_id = :siteId', { siteId })
.andWhere('user.delete_time IS NULL');
if (username) {
queryBuilder.andWhere('user.username LIKE :username', { username: `%${username}%` });
}
if (nickname) {
queryBuilder.andWhere('user.nickname LIKE :nickname', { nickname: `%${nickname}%` });
}
if (email) {
queryBuilder.andWhere('user.email LIKE :email', { email: `%${email}%` });
}
if (mobile) {
queryBuilder.andWhere('user.mobile LIKE :mobile', { mobile: `%${mobile}%` });
}
if (status !== undefined) {
queryBuilder.andWhere('user.status = :status', { status });
}
if (dept_id) {
queryBuilder.andWhere('user.dept_id = :dept_id', { dept_id });
}
const [users, total] = await queryBuilder
.orderBy('user.sort', 'ASC')
.addOrderBy('user.id', 'DESC')
.skip((page - 1) * pageSize)
.take(pageSize)
.getManyAndCount();
return {
list: users,
total,
page,
pageSize,
};
}
/**
* 根据ID获取用户
*/
async getUserById(id: number, siteId: number): Promise<SysUser | null> {
return await this.userRepository.findOne({
where: { id, site_id: siteId },
relations: ['roles'],
});
}
/**
* 根据用户名获取用户
*/
async getUserByUsername(username: string, siteId: number): Promise<SysUser | null> {
return await this.userRepository.findOne({
where: { username, site_id: siteId },
relations: ['roles'],
});
}
/**
* 删除用户
*/
async deleteUser(id: number, siteId: number): Promise<void> {
await this.userRepository.softDelete({ id, site_id: siteId });
}
/**
* 批量更新用户状态
*/
async batchUpdateStatus(ids: number[], status: number, siteId: number): Promise<void> {
await this.userRepository
.createQueryBuilder()
.update(SysUser)
.set({ status })
.whereInIds(ids)
.andWhere('site_id = :siteId', { siteId })
.execute();
}
/**
* 重置用户密码
*/
async resetPassword(id: number, password: string, siteId: number): Promise<void> {
const hashedPassword = await bcrypt.hash(password, 10);
await this.userRepository
.createQueryBuilder()
.update(SysUser)
.set({ password: hashedPassword })
.where('id = :id', { id })
.andWhere('site_id = :siteId', { siteId })
.execute();
}
/**
* 验证用户密码
*/
async validatePassword(user: SysUser, password: string): Promise<boolean> {
return await bcrypt.compare(password, user.password);
}
}

View File

@@ -0,0 +1,40 @@
import { Module, forwardRef } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from '../auth/auth.module';
import { RbacModule } from '../rbac/rbac.module';
import { DatabaseModule } from '@wwjCore/database/databaseModule';
import { SysUser } from './entities/SysUser';
// Core Services
import { CoreUserService } from './services/core/CoreUserService';
// Admin Services
import { UserAdminService } from './services/admin/UserAdminService';
// Controllers
import { UserController } from './controllers/adminapi/UserController';
@Module({
imports: [
forwardRef(() => AuthModule),
forwardRef(() => RbacModule),
DatabaseModule,
TypeOrmModule.forFeature([SysUser]),
],
providers: [
// Core Services
CoreUserService,
// Admin Services
UserAdminService,
],
controllers: [UserController],
exports: [
// Core Services
CoreUserService,
// Admin Services
UserAdminService,
],
})
export class UserModule {}