feat: 初始化 WWJ Cloud 企业级框架项目
- 后端:基于 NestJS 的分层架构设计 - 前端:基于 VbenAdmin + Element Plus 的管理系统 - 支持 SaaS + 独立版双架构模式 - 完整的用户权限管理系统 - 系统设置、文件上传、通知等核心功能 - 多租户支持和插件化扩展架构
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { Body, Controller, Get, Put, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { EmailSettingsService } from './email-settings.service';
|
||||
import { UpdateEmailSettingsDto, type EmailSettingsVo } from './email-settings.dto';
|
||||
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
|
||||
import { Roles } from '../../auth/roles.decorator';
|
||||
import { RolesGuard } from '../../auth/guards/roles.guard';
|
||||
|
||||
@ApiTags('Settings/Email')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('super', 'admin')
|
||||
@Controller('settings/email')
|
||||
export class EmailSettingsController {
|
||||
constructor(private readonly service: EmailSettingsService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '获取邮件设置' })
|
||||
async get(): Promise<{ code: number; data: EmailSettingsVo }> {
|
||||
const data = await this.service.getSettings();
|
||||
return { code: 0, data };
|
||||
}
|
||||
|
||||
@Put()
|
||||
@ApiOperation({ summary: '更新邮件设置' })
|
||||
async update(@Body() dto: UpdateEmailSettingsDto): Promise<{ code: number; data: EmailSettingsVo }> {
|
||||
const data = await this.service.updateSettings(dto);
|
||||
return { code: 0, data };
|
||||
}
|
||||
}
|
||||
36
wwjcloud/src/common/settings/email/email-settings.dto.ts
Normal file
36
wwjcloud/src/common/settings/email/email-settings.dto.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { IsBoolean, IsEmail, IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
|
||||
|
||||
export class UpdateEmailSettingsDto {
|
||||
@IsBoolean()
|
||||
enabled!: boolean;
|
||||
|
||||
@IsString()
|
||||
host!: string;
|
||||
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(65535)
|
||||
port!: number;
|
||||
|
||||
@IsBoolean()
|
||||
secure!: boolean;
|
||||
|
||||
@IsString()
|
||||
user!: string;
|
||||
|
||||
@IsString()
|
||||
pass!: string;
|
||||
|
||||
@IsEmail()
|
||||
from!: string;
|
||||
}
|
||||
|
||||
export interface EmailSettingsVo {
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
port: number;
|
||||
secure: boolean;
|
||||
user: string;
|
||||
pass: string;
|
||||
from: string;
|
||||
}
|
||||
42
wwjcloud/src/common/settings/email/email-settings.service.ts
Normal file
42
wwjcloud/src/common/settings/email/email-settings.service.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type { EmailSettingsVo } from './email-settings.dto';
|
||||
|
||||
const SETTINGS_DIR = path.resolve(process.cwd(), 'config', 'runtime');
|
||||
const SETTINGS_FILE = path.resolve(SETTINGS_DIR, 'email.settings.json');
|
||||
|
||||
const DEFAULT_SETTINGS: EmailSettingsVo = {
|
||||
enabled: false,
|
||||
host: '',
|
||||
port: 465,
|
||||
secure: true,
|
||||
user: '',
|
||||
pass: '',
|
||||
from: '',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class EmailSettingsService {
|
||||
async getSettings(): Promise<EmailSettingsVo> {
|
||||
try {
|
||||
const buf = await fs.promises.readFile(SETTINGS_FILE, 'utf8');
|
||||
const json = JSON.parse(buf);
|
||||
return { ...DEFAULT_SETTINGS, ...json };
|
||||
} catch {
|
||||
return { ...DEFAULT_SETTINGS };
|
||||
}
|
||||
}
|
||||
|
||||
async updateSettings(patch: Partial<EmailSettingsVo>): Promise<EmailSettingsVo> {
|
||||
const current = await this.getSettings();
|
||||
const next: EmailSettingsVo = { ...current, ...patch };
|
||||
await fs.promises.mkdir(SETTINGS_DIR, { recursive: true });
|
||||
await fs.promises.writeFile(SETTINGS_FILE, JSON.stringify(next, null, 2), 'utf8');
|
||||
return next;
|
||||
}
|
||||
|
||||
static getSettingsPath() {
|
||||
return SETTINGS_FILE;
|
||||
}
|
||||
}
|
||||
11
wwjcloud/src/common/settings/email/email.module.ts
Normal file
11
wwjcloud/src/common/settings/email/email.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { EmailService } from './email.service';
|
||||
import { EmailSettingsService } from './email-settings.service';
|
||||
import { EmailSettingsController } from './email-settings.controller';
|
||||
|
||||
@Module({
|
||||
providers: [EmailService, EmailSettingsService],
|
||||
controllers: [EmailSettingsController],
|
||||
exports: [EmailService, EmailSettingsService],
|
||||
})
|
||||
export class EmailModule {}
|
||||
10
wwjcloud/src/common/settings/email/email.service.ts
Normal file
10
wwjcloud/src/common/settings/email/email.service.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class EmailService {
|
||||
private readonly logger = new Logger(EmailService.name);
|
||||
|
||||
async send(to: string, subject: string, content: string): Promise<void> {
|
||||
this.logger.log(`Mock send email to ${to} subject=${subject}`);
|
||||
}
|
||||
}
|
||||
24
wwjcloud/src/common/settings/index.ts
Normal file
24
wwjcloud/src/common/settings/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export { SettingsModule } from './settings.module';
|
||||
|
||||
export { EmailModule } from './email/email.module';
|
||||
export { EmailService } from './email/email.service';
|
||||
export { EmailSettingsService } from './email/email-settings.service';
|
||||
|
||||
export { SmsModule } from './sms/sms.module';
|
||||
export { SmsService } from './sms/sms.service';
|
||||
export { SmsSettingsService } from './sms/sms-settings.service';
|
||||
|
||||
export { StorageModule } from './storage/storage.module';
|
||||
export { StorageService } from './storage/storage.service';
|
||||
export { StorageSettingsService } from './storage/storage-settings.service';
|
||||
|
||||
export { PaymentModule } from './payment/payment.module';
|
||||
export { PaymentService } from './payment/payment.service';
|
||||
export { PaymentSettingsService } from './payment/payment-settings.service';
|
||||
|
||||
export { LoginModule } from './login/login.module';
|
||||
export { LoginSettingsService } from './login/login-settings.service';
|
||||
|
||||
export { SiteModule } from './site/site.module';
|
||||
export { SiteSettingsService } from './site/site-settings.service';
|
||||
export { Site } from './site/site.entity';
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Body, Controller, Get, Put, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { UpdateLoginSettingsDto, type LoginSettingsVo } from './login-settings.dto';
|
||||
import { LoginSettingsService } from './login-settings.service';
|
||||
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
|
||||
import { Roles } from '../../auth/roles.decorator';
|
||||
import { RolesGuard } from '../../auth/guards/roles.guard';
|
||||
|
||||
@ApiTags('Settings/Login')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('super', 'admin')
|
||||
@Controller('settings/login')
|
||||
export class LoginSettingsController {
|
||||
constructor(private readonly service: LoginSettingsService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '获取登录设置' })
|
||||
async get(): Promise<{ code: number; data: LoginSettingsVo }> {
|
||||
const data = await this.service.getSettings();
|
||||
return { code: 0, data };
|
||||
}
|
||||
|
||||
@Put()
|
||||
@ApiOperation({ summary: '更新登录设置' })
|
||||
async update(@Body() dto: UpdateLoginSettingsDto): Promise<{ code: number; data: LoginSettingsVo }> {
|
||||
const data = await this.service.updateSettings(dto);
|
||||
return { code: 0, data };
|
||||
}
|
||||
}
|
||||
24
wwjcloud/src/common/settings/login/login-settings.dto.ts
Normal file
24
wwjcloud/src/common/settings/login/login-settings.dto.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateLoginSettingsDto {
|
||||
@IsBoolean()
|
||||
isCaptcha!: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
bg?: string;
|
||||
|
||||
@IsBoolean()
|
||||
isSiteCaptcha!: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
siteBg?: string;
|
||||
}
|
||||
|
||||
export interface LoginSettingsVo {
|
||||
isCaptcha: boolean;
|
||||
bg?: string;
|
||||
isSiteCaptcha: boolean;
|
||||
siteBg?: string;
|
||||
}
|
||||
39
wwjcloud/src/common/settings/login/login-settings.service.ts
Normal file
39
wwjcloud/src/common/settings/login/login-settings.service.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type { LoginSettingsVo } from './login-settings.dto';
|
||||
|
||||
const SETTINGS_DIR = path.resolve(process.cwd(), 'config', 'runtime');
|
||||
const SETTINGS_FILE = path.resolve(SETTINGS_DIR, 'login.settings.json');
|
||||
|
||||
const DEFAULT_SETTINGS: LoginSettingsVo = {
|
||||
isCaptcha: false,
|
||||
bg: '',
|
||||
isSiteCaptcha: false,
|
||||
siteBg: '',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class LoginSettingsService {
|
||||
async getSettings(): Promise<LoginSettingsVo> {
|
||||
try {
|
||||
const buf = await fs.promises.readFile(SETTINGS_FILE, 'utf8');
|
||||
const json = JSON.parse(buf);
|
||||
return { ...DEFAULT_SETTINGS, ...json } as LoginSettingsVo;
|
||||
} catch {
|
||||
return { ...DEFAULT_SETTINGS };
|
||||
}
|
||||
}
|
||||
|
||||
async updateSettings(patch: Partial<LoginSettingsVo>): Promise<LoginSettingsVo> {
|
||||
const current = await this.getSettings();
|
||||
const next: LoginSettingsVo = { ...current, ...patch };
|
||||
await fs.promises.mkdir(SETTINGS_DIR, { recursive: true });
|
||||
await fs.promises.writeFile(SETTINGS_FILE, JSON.stringify(next, null, 2), 'utf8');
|
||||
return next;
|
||||
}
|
||||
|
||||
static getSettingsPath() {
|
||||
return SETTINGS_FILE;
|
||||
}
|
||||
}
|
||||
10
wwjcloud/src/common/settings/login/login.module.ts
Normal file
10
wwjcloud/src/common/settings/login/login.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { LoginSettingsService } from './login-settings.service';
|
||||
import { LoginSettingsController } from './login-settings.controller';
|
||||
|
||||
@Module({
|
||||
providers: [LoginSettingsService],
|
||||
controllers: [LoginSettingsController],
|
||||
exports: [LoginSettingsService],
|
||||
})
|
||||
export class LoginModule {}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Body, Controller, Get, Put, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { PaymentSettingsService } from './payment-settings.service';
|
||||
import { UpdatePaymentSettingsDto, type PaymentSettingsVo } from './payment-settings.dto';
|
||||
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
|
||||
import { Roles } from '../../auth/roles.decorator';
|
||||
import { RolesGuard } from '../../auth/guards/roles.guard';
|
||||
|
||||
@ApiTags('Settings/Payment')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('super', 'admin')
|
||||
@Controller('settings/payment')
|
||||
export class PaymentSettingsController {
|
||||
constructor(private readonly service: PaymentSettingsService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '获取支付设置' })
|
||||
async get(): Promise<{ code: number; data: PaymentSettingsVo }> {
|
||||
const data = await this.service.getSettings();
|
||||
return { code: 0, data };
|
||||
}
|
||||
|
||||
@Put()
|
||||
@ApiOperation({ summary: '更新支付设置' })
|
||||
async update(
|
||||
@Body() dto: UpdatePaymentSettingsDto,
|
||||
): Promise<{ code: number; data: PaymentSettingsVo }> {
|
||||
const data = await this.service.updateSettings(dto);
|
||||
return { code: 0, data };
|
||||
}
|
||||
}
|
||||
20
wwjcloud/src/common/settings/payment/payment-settings.dto.ts
Normal file
20
wwjcloud/src/common/settings/payment/payment-settings.dto.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { IsBoolean, IsObject, IsOptional } from 'class-validator';
|
||||
|
||||
export class UpdatePaymentSettingsDto {
|
||||
@IsBoolean()
|
||||
enabled!: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
alipay?: Record<string, string>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
wechatpay?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface PaymentSettingsVo {
|
||||
enabled: boolean;
|
||||
alipay?: Record<string, string>;
|
||||
wechatpay?: Record<string, string>;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type { PaymentSettingsVo } from './payment-settings.dto';
|
||||
|
||||
const SETTINGS_DIR = path.resolve(process.cwd(), 'config', 'runtime');
|
||||
const SETTINGS_FILE = path.resolve(SETTINGS_DIR, 'payment.settings.json');
|
||||
|
||||
const DEFAULT_SETTINGS: PaymentSettingsVo = {
|
||||
enabled: false,
|
||||
alipay: {},
|
||||
wechatpay: {},
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PaymentSettingsService {
|
||||
async getSettings(): Promise<PaymentSettingsVo> {
|
||||
try {
|
||||
const buf = await fs.promises.readFile(SETTINGS_FILE, 'utf8');
|
||||
const json = JSON.parse(buf);
|
||||
return { ...DEFAULT_SETTINGS, ...json };
|
||||
} catch {
|
||||
return { ...DEFAULT_SETTINGS };
|
||||
}
|
||||
}
|
||||
|
||||
async updateSettings(patch: Partial<PaymentSettingsVo>): Promise<PaymentSettingsVo> {
|
||||
const current = await this.getSettings();
|
||||
const next: PaymentSettingsVo = { ...current, ...patch };
|
||||
await fs.promises.mkdir(SETTINGS_DIR, { recursive: true });
|
||||
await fs.promises.writeFile(SETTINGS_FILE, JSON.stringify(next, null, 2), 'utf8');
|
||||
return next;
|
||||
}
|
||||
|
||||
static getSettingsPath() {
|
||||
return SETTINGS_FILE;
|
||||
}
|
||||
}
|
||||
11
wwjcloud/src/common/settings/payment/payment.module.ts
Normal file
11
wwjcloud/src/common/settings/payment/payment.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PaymentService } from './payment.service';
|
||||
import { PaymentSettingsService } from './payment-settings.service';
|
||||
import { PaymentSettingsController } from './payment-settings.controller';
|
||||
|
||||
@Module({
|
||||
providers: [PaymentService, PaymentSettingsService],
|
||||
controllers: [PaymentSettingsController],
|
||||
exports: [PaymentService, PaymentSettingsService],
|
||||
})
|
||||
export class PaymentModule {}
|
||||
8
wwjcloud/src/common/settings/payment/payment.service.ts
Normal file
8
wwjcloud/src/common/settings/payment/payment.service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentService {
|
||||
async createPayment(orderId: string, amount: number) {
|
||||
return { orderId, amount, status: 'mock' };
|
||||
}
|
||||
}
|
||||
30
wwjcloud/src/common/settings/settings.module.ts
Normal file
30
wwjcloud/src/common/settings/settings.module.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { StorageModule } from './storage/storage.module';
|
||||
import { PaymentModule } from './payment/payment.module';
|
||||
import { EmailModule } from './email/email.module';
|
||||
import { SmsModule } from './sms/sms.module';
|
||||
import { UploadSettingsModule } from './upload/upload-settings.module';
|
||||
import { LoginModule } from './login/login.module';
|
||||
import { SiteModule } from './site/site.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
StorageModule,
|
||||
PaymentModule,
|
||||
EmailModule,
|
||||
SmsModule,
|
||||
UploadSettingsModule,
|
||||
LoginModule,
|
||||
SiteModule,
|
||||
],
|
||||
exports: [
|
||||
StorageModule,
|
||||
PaymentModule,
|
||||
EmailModule,
|
||||
SmsModule,
|
||||
UploadSettingsModule,
|
||||
LoginModule,
|
||||
SiteModule,
|
||||
],
|
||||
})
|
||||
export class SettingsModule {}
|
||||
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Put,
|
||||
Body,
|
||||
Post,
|
||||
UseGuards,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../../auth/guards/roles.guard';
|
||||
import { Roles } from '../../auth/roles.decorator';
|
||||
import { SiteSettingsService } from './site-settings.service';
|
||||
import { UpdateSiteSettingsDto } from './site-settings.dto';
|
||||
|
||||
@ApiTags('站点设置')
|
||||
@Controller('system/settings/basic')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@ApiBearerAuth()
|
||||
export class SiteSettingsController {
|
||||
constructor(private readonly siteSettingsService: SiteSettingsService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '获取站点设置' })
|
||||
@ApiResponse({ status: 200, description: '成功获取站点设置' })
|
||||
@Roles('super', 'admin')
|
||||
async getSiteSettings() {
|
||||
return this.siteSettingsService.getSiteSettings();
|
||||
}
|
||||
|
||||
@Put()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '更新站点设置' })
|
||||
@ApiResponse({ status: 200, description: '站点设置更新成功' })
|
||||
@Roles('super', 'admin')
|
||||
async updateSiteSettings(@Body() updateSiteSettingsDto: UpdateSiteSettingsDto) {
|
||||
return this.siteSettingsService.updateSiteSettings(updateSiteSettingsDto);
|
||||
}
|
||||
|
||||
@Post('reset')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '重置站点设置为默认值' })
|
||||
@ApiResponse({ status: 200, description: '站点设置重置成功' })
|
||||
@Roles('super')
|
||||
async resetSiteSettings() {
|
||||
return this.siteSettingsService.resetSiteSettings();
|
||||
}
|
||||
}
|
||||
91
wwjcloud/src/common/settings/site/site-settings.dto.ts
Normal file
91
wwjcloud/src/common/settings/site/site-settings.dto.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
/**
|
||||
* 更新站点设置DTO
|
||||
*/
|
||||
export class UpdateSiteSettingsDto {
|
||||
@ApiProperty({ description: '网站名称', required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
site_name?: string;
|
||||
|
||||
@ApiProperty({ description: '网站标题', required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
site_title?: string;
|
||||
|
||||
@ApiProperty({ description: '网站关键词', required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
site_keywords?: string;
|
||||
|
||||
@ApiProperty({ description: '网站描述', required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
site_description?: string;
|
||||
|
||||
@ApiProperty({ description: '网站Logo', required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
site_logo?: string;
|
||||
|
||||
@ApiProperty({ description: '网站图标', required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
site_favicon?: string;
|
||||
|
||||
@ApiProperty({ description: 'ICP备案号', required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
icp_number?: string;
|
||||
|
||||
@ApiProperty({ description: '版权信息', required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
copyright?: string;
|
||||
|
||||
@ApiProperty({ description: '网站状态', required: false })
|
||||
@IsOptional()
|
||||
site_status?: number;
|
||||
|
||||
@ApiProperty({ description: '关闭原因', required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
close_reason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 站点设置响应DTO
|
||||
*/
|
||||
export class SiteSettingsDto {
|
||||
@ApiProperty({ description: '网站名称' })
|
||||
site_name: string;
|
||||
|
||||
@ApiProperty({ description: '网站标题' })
|
||||
site_title: string;
|
||||
|
||||
@ApiProperty({ description: '网站关键词' })
|
||||
site_keywords: string;
|
||||
|
||||
@ApiProperty({ description: '网站描述' })
|
||||
site_description: string;
|
||||
|
||||
@ApiProperty({ description: '网站Logo' })
|
||||
site_logo: string;
|
||||
|
||||
@ApiProperty({ description: '网站图标' })
|
||||
site_favicon: string;
|
||||
|
||||
@ApiProperty({ description: 'ICP备案号' })
|
||||
icp_number: string;
|
||||
|
||||
@ApiProperty({ description: '版权信息' })
|
||||
copyright: string;
|
||||
|
||||
@ApiProperty({ description: '网站状态' })
|
||||
site_status: number;
|
||||
|
||||
@ApiProperty({ description: '关闭原因' })
|
||||
close_reason: string;
|
||||
}
|
||||
133
wwjcloud/src/common/settings/site/site-settings.service.ts
Normal file
133
wwjcloud/src/common/settings/site/site-settings.service.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Site } from './site.entity';
|
||||
import { UpdateSiteSettingsDto } from './site-settings.dto';
|
||||
|
||||
@Injectable()
|
||||
export class SiteSettingsService {
|
||||
constructor(
|
||||
@InjectRepository(Site)
|
||||
private readonly siteRepository: Repository<Site>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取站点设置
|
||||
*/
|
||||
async getSiteSettings() {
|
||||
// 获取默认站点(id = 1)
|
||||
const site = await this.siteRepository.findOne({
|
||||
where: { id: 1 },
|
||||
});
|
||||
|
||||
if (!site) {
|
||||
// 如果没有找到站点,返回默认值
|
||||
return {
|
||||
site_name: 'WWJ Cloud',
|
||||
site_title: 'WWJ Cloud 企业级框架',
|
||||
site_keywords: 'WWJ Cloud,企业级框架,NestJS,VbenAdmin',
|
||||
site_description: 'WWJ Cloud 企业级框架 - 快速开发SAAS多用户系统后台管理框架',
|
||||
site_logo: '',
|
||||
site_favicon: '',
|
||||
icp_number: '',
|
||||
copyright: '',
|
||||
site_status: 1,
|
||||
close_reason: '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
site_name: site.site_name || '',
|
||||
site_title: site.site_title || '',
|
||||
site_keywords: site.site_keywords || '',
|
||||
site_description: site.site_description || '',
|
||||
site_logo: site.site_logo || '',
|
||||
site_favicon: site.site_favicon || '',
|
||||
icp_number: site.icp_number || '',
|
||||
copyright: site.copyright || '',
|
||||
site_status: site.site_status || 1,
|
||||
close_reason: site.close_reason || '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新站点设置
|
||||
*/
|
||||
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: 1 },
|
||||
});
|
||||
|
||||
if (!site) {
|
||||
// 创建默认站点
|
||||
site = this.siteRepository.create({
|
||||
id: 1,
|
||||
site_name: site_name || 'WWJ Cloud',
|
||||
site_title: site_title || 'WWJ Cloud 企业级框架',
|
||||
site_keywords: site_keywords || '',
|
||||
site_description: site_description || '',
|
||||
site_logo: site_logo || '',
|
||||
site_favicon: site_favicon || '',
|
||||
icp_number: icp_number || '',
|
||||
copyright: copyright || '',
|
||||
site_status: site_status || 1,
|
||||
close_reason: 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: '站点设置更新成功' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置站点设置为默认值
|
||||
*/
|
||||
async resetSiteSettings() {
|
||||
// 删除现有站点配置
|
||||
await this.siteRepository.delete({ id: 1 });
|
||||
|
||||
// 创建默认站点配置
|
||||
const defaultSite = this.siteRepository.create({
|
||||
id: 1,
|
||||
site_name: 'WWJ Cloud',
|
||||
site_title: 'WWJ Cloud 企业级框架',
|
||||
site_keywords: 'WWJ Cloud,企业级框架,NestJS,VbenAdmin',
|
||||
site_description: 'WWJ Cloud 企业级框架 - 快速开发SAAS多用户系统后台管理框架',
|
||||
site_logo: '',
|
||||
site_favicon: '',
|
||||
icp_number: '',
|
||||
copyright: '',
|
||||
site_status: 1,
|
||||
close_reason: '',
|
||||
});
|
||||
|
||||
await this.siteRepository.save(defaultSite);
|
||||
return { message: '站点设置已重置为默认值' };
|
||||
}
|
||||
}
|
||||
41
wwjcloud/src/common/settings/site/site.entity.ts
Normal file
41
wwjcloud/src/common/settings/site/site.entity.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 站点信息实体
|
||||
* 对应数据库表:site
|
||||
*/
|
||||
@Entity('site')
|
||||
export class Site {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, comment: '网站名称' })
|
||||
site_name: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, comment: '网站标题' })
|
||||
site_title: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, comment: '网站关键词' })
|
||||
site_keywords: string;
|
||||
|
||||
@Column({ type: 'text', comment: '网站描述' })
|
||||
site_description: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, comment: '网站Logo' })
|
||||
site_logo: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, comment: '网站图标' })
|
||||
site_favicon: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, comment: 'ICP备案号' })
|
||||
icp_number: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, comment: '版权信息' })
|
||||
copyright: string;
|
||||
|
||||
@Column({ type: 'tinyint', default: 1, comment: '网站状态 1:开启 0:关闭' })
|
||||
site_status: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, comment: '关闭原因' })
|
||||
close_reason: string;
|
||||
}
|
||||
13
wwjcloud/src/common/settings/site/site.module.ts
Normal file
13
wwjcloud/src/common/settings/site/site.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { SiteSettingsController } from './site-settings.controller';
|
||||
import { SiteSettingsService } from './site-settings.service';
|
||||
import { Site } from './site.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Site])],
|
||||
controllers: [SiteSettingsController],
|
||||
providers: [SiteSettingsService],
|
||||
exports: [SiteSettingsService],
|
||||
})
|
||||
export class SiteModule {}
|
||||
30
wwjcloud/src/common/settings/sms/sms-settings.controller.ts
Normal file
30
wwjcloud/src/common/settings/sms/sms-settings.controller.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Body, Controller, Get, Put, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { SmsSettingsService } from './sms-settings.service';
|
||||
import { UpdateSmsSettingsDto, type SmsSettingsVo } from './sms-settings.dto';
|
||||
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
|
||||
import { Roles } from '../../auth/roles.decorator';
|
||||
import { RolesGuard } from '../../auth/guards/roles.guard';
|
||||
|
||||
@ApiTags('Settings/Sms')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('super', 'admin')
|
||||
@Controller('settings/sms')
|
||||
export class SmsSettingsController {
|
||||
constructor(private readonly service: SmsSettingsService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '获取短信设置' })
|
||||
async get(): Promise<{ code: number; data: SmsSettingsVo }> {
|
||||
const data = await this.service.getSettings();
|
||||
return { code: 0, data };
|
||||
}
|
||||
|
||||
@Put()
|
||||
@ApiOperation({ summary: '更新短信设置' })
|
||||
async update(@Body() dto: UpdateSmsSettingsDto): Promise<{ code: number; data: SmsSettingsVo }> {
|
||||
const data = await this.service.updateSettings(dto);
|
||||
return { code: 0, data };
|
||||
}
|
||||
}
|
||||
39
wwjcloud/src/common/settings/sms/sms-settings.dto.ts
Normal file
39
wwjcloud/src/common/settings/sms/sms-settings.dto.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { IsBoolean, IsIn, IsObject, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateSmsSettingsDto {
|
||||
@IsBoolean()
|
||||
enabled!: boolean;
|
||||
|
||||
@IsIn(['mock', 'aliyun', 'tencent'])
|
||||
provider!: 'mock' | 'aliyun' | 'tencent';
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
signName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
accessKeyId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
accessKeySecret?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
region?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
templates?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface SmsSettingsVo {
|
||||
enabled: boolean;
|
||||
provider: 'mock' | 'aliyun' | 'tencent';
|
||||
signName?: string;
|
||||
accessKeyId?: string;
|
||||
accessKeySecret?: string;
|
||||
region?: string;
|
||||
templates?: Record<string, string>;
|
||||
}
|
||||
42
wwjcloud/src/common/settings/sms/sms-settings.service.ts
Normal file
42
wwjcloud/src/common/settings/sms/sms-settings.service.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type { SmsSettingsVo } from './sms-settings.dto';
|
||||
|
||||
const SETTINGS_DIR = path.resolve(process.cwd(), 'config', 'runtime');
|
||||
const SETTINGS_FILE = path.resolve(SETTINGS_DIR, 'sms.settings.json');
|
||||
|
||||
const DEFAULT_SETTINGS: SmsSettingsVo = {
|
||||
enabled: false,
|
||||
provider: 'mock',
|
||||
signName: '',
|
||||
accessKeyId: '',
|
||||
accessKeySecret: '',
|
||||
region: 'cn-hangzhou',
|
||||
templates: {},
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SmsSettingsService {
|
||||
async getSettings(): Promise<SmsSettingsVo> {
|
||||
try {
|
||||
const buf = await fs.promises.readFile(SETTINGS_FILE, 'utf8');
|
||||
const json = JSON.parse(buf);
|
||||
return { ...DEFAULT_SETTINGS, ...json };
|
||||
} catch {
|
||||
return { ...DEFAULT_SETTINGS };
|
||||
}
|
||||
}
|
||||
|
||||
async updateSettings(patch: Partial<SmsSettingsVo>): Promise<SmsSettingsVo> {
|
||||
const current = await this.getSettings();
|
||||
const next: SmsSettingsVo = { ...current, ...patch };
|
||||
await fs.promises.mkdir(SETTINGS_DIR, { recursive: true });
|
||||
await fs.promises.writeFile(SETTINGS_FILE, JSON.stringify(next, null, 2), 'utf8');
|
||||
return next;
|
||||
}
|
||||
|
||||
static getSettingsPath() {
|
||||
return SETTINGS_FILE;
|
||||
}
|
||||
}
|
||||
11
wwjcloud/src/common/settings/sms/sms.module.ts
Normal file
11
wwjcloud/src/common/settings/sms/sms.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SmsService } from './sms.service';
|
||||
import { SmsSettingsService } from './sms-settings.service';
|
||||
import { SmsSettingsController } from './sms-settings.controller';
|
||||
|
||||
@Module({
|
||||
providers: [SmsService, SmsSettingsService],
|
||||
controllers: [SmsSettingsController],
|
||||
exports: [SmsService, SmsSettingsService],
|
||||
})
|
||||
export class SmsModule {}
|
||||
16
wwjcloud/src/common/settings/sms/sms.service.ts
Normal file
16
wwjcloud/src/common/settings/sms/sms.service.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class SmsService {
|
||||
private readonly logger = new Logger(SmsService.name);
|
||||
|
||||
async send(
|
||||
to: string,
|
||||
templateId: string,
|
||||
params: Record<string, any> = {},
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`Mock send sms to ${to} templateId=${templateId} params=${JSON.stringify(params)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Body, Controller, Get, Put, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { StorageSettingsService } from './storage-settings.service';
|
||||
import { UpdateStorageSettingsDto, type StorageSettingsVo } from './storage-settings.dto';
|
||||
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
|
||||
import { Roles } from '../../auth/roles.decorator';
|
||||
import { RolesGuard } from '../../auth/guards/roles.guard';
|
||||
|
||||
@ApiTags('Settings/Storage')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('super', 'admin')
|
||||
@Controller('settings/storage')
|
||||
export class StorageSettingsController {
|
||||
constructor(private readonly service: StorageSettingsService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '获取存储设置' })
|
||||
async get(): Promise<{ code: number; data: StorageSettingsVo }> {
|
||||
const data = await this.service.getSettings();
|
||||
return { code: 0, data };
|
||||
}
|
||||
|
||||
@Put()
|
||||
@ApiOperation({ summary: '更新存储设置' })
|
||||
async update(
|
||||
@Body() dto: UpdateStorageSettingsDto,
|
||||
): Promise<{ code: number; data: StorageSettingsVo }> {
|
||||
const data = await this.service.updateSettings(dto);
|
||||
return { code: 0, data };
|
||||
}
|
||||
}
|
||||
54
wwjcloud/src/common/settings/storage/storage-settings.dto.ts
Normal file
54
wwjcloud/src/common/settings/storage/storage-settings.dto.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { IsBoolean, IsIn, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateStorageSettingsDto {
|
||||
@IsBoolean()
|
||||
enabled!: boolean;
|
||||
|
||||
@IsIn(['local', 'aliyun', 'tencent', 'qiniu', 's3', 'minio'])
|
||||
provider!: 'local' | 'aliyun' | 'tencent' | 'qiniu' | 's3' | 'minio';
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
accessKeyId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
accessKeySecret?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
bucket?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
region?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
endpoint?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
domain?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
folder?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isPrivate?: boolean;
|
||||
}
|
||||
|
||||
export interface StorageSettingsVo {
|
||||
enabled: boolean;
|
||||
provider: 'local' | 'aliyun' | 'tencent' | 'qiniu' | 's3' | 'minio';
|
||||
accessKeyId?: string;
|
||||
accessKeySecret?: string;
|
||||
bucket?: string;
|
||||
region?: string;
|
||||
endpoint?: string;
|
||||
domain?: string;
|
||||
folder?: string;
|
||||
isPrivate?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type { StorageSettingsVo } from './storage-settings.dto';
|
||||
|
||||
const SETTINGS_DIR = path.resolve(process.cwd(), 'config', 'runtime');
|
||||
const SETTINGS_FILE = path.resolve(SETTINGS_DIR, 'storage.settings.json');
|
||||
|
||||
const DEFAULT_SETTINGS: StorageSettingsVo = {
|
||||
enabled: false,
|
||||
provider: 'local',
|
||||
accessKeyId: '',
|
||||
accessKeySecret: '',
|
||||
bucket: '',
|
||||
region: '',
|
||||
endpoint: '',
|
||||
domain: '',
|
||||
folder: '',
|
||||
isPrivate: false,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class StorageSettingsService {
|
||||
async getSettings(): Promise<StorageSettingsVo> {
|
||||
try {
|
||||
const buf = await fs.promises.readFile(SETTINGS_FILE, 'utf8');
|
||||
const json = JSON.parse(buf);
|
||||
return { ...DEFAULT_SETTINGS, ...json };
|
||||
} catch {
|
||||
return { ...DEFAULT_SETTINGS };
|
||||
}
|
||||
}
|
||||
|
||||
async updateSettings(
|
||||
patch: Partial<StorageSettingsVo>,
|
||||
): Promise<StorageSettingsVo> {
|
||||
const current = await this.getSettings();
|
||||
const next: StorageSettingsVo = { ...current, ...patch };
|
||||
await fs.promises.mkdir(SETTINGS_DIR, { recursive: true });
|
||||
await fs.promises.writeFile(
|
||||
SETTINGS_FILE,
|
||||
JSON.stringify(next, null, 2),
|
||||
'utf8',
|
||||
);
|
||||
return next;
|
||||
}
|
||||
|
||||
static getSettingsPath() {
|
||||
return SETTINGS_FILE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
|
||||
@Controller('storage')
|
||||
export class StorageController {
|
||||
@Get('health')
|
||||
health() {
|
||||
return { ok: true };
|
||||
}
|
||||
}
|
||||
12
wwjcloud/src/common/settings/storage/storage.module.ts
Normal file
12
wwjcloud/src/common/settings/storage/storage.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { StorageService } from './storage.service';
|
||||
import { StorageController } from './storage.controller';
|
||||
import { StorageSettingsService } from './storage-settings.service';
|
||||
import { StorageSettingsController } from './storage-settings.controller';
|
||||
|
||||
@Module({
|
||||
providers: [StorageService, StorageSettingsService],
|
||||
exports: [StorageService, StorageSettingsService],
|
||||
controllers: [StorageController, StorageSettingsController],
|
||||
})
|
||||
export class StorageModule {}
|
||||
12
wwjcloud/src/common/settings/storage/storage.service.ts
Normal file
12
wwjcloud/src/common/settings/storage/storage.service.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class StorageService {
|
||||
async saveObject(
|
||||
key: string,
|
||||
data: Buffer | string,
|
||||
): Promise<{ key: string }> {
|
||||
// mock save
|
||||
return { key };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Body, Controller, Get, Put, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { UploadSettingsService } from './upload-settings.service';
|
||||
import {
|
||||
UpdateUploadSettingsDto,
|
||||
type UploadSettingsVo,
|
||||
} from './upload-settings.dto';
|
||||
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
|
||||
import { Roles } from '../../auth/roles.decorator';
|
||||
import { RolesGuard } from '../../auth/guards/roles.guard';
|
||||
|
||||
@ApiTags('Settings/Upload')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('super', 'admin')
|
||||
@Controller('settings/upload')
|
||||
export class UploadSettingsController {
|
||||
constructor(private readonly service: UploadSettingsService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '获取上传设置' })
|
||||
async get(): Promise<{ code: number; data: UploadSettingsVo }> {
|
||||
const data = await this.service.getSettings();
|
||||
return { code: 0, data };
|
||||
}
|
||||
|
||||
@Put()
|
||||
@ApiOperation({ summary: '更新上传设置' })
|
||||
async update(
|
||||
@Body() dto: UpdateUploadSettingsDto,
|
||||
): Promise<{ code: number; data: UploadSettingsVo }> {
|
||||
const data = await this.service.updateSettings(dto);
|
||||
return { code: 0, data };
|
||||
}
|
||||
}
|
||||
25
wwjcloud/src/common/settings/upload/upload-settings.dto.ts
Normal file
25
wwjcloud/src/common/settings/upload/upload-settings.dto.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { IsArray, IsIn, IsInt, Max, Min } from 'class-validator';
|
||||
|
||||
export class UpdateUploadSettingsDto {
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(50) // 受 fastify-multipart 当前上限约束(main.ts: fileSize=50MB),后续可提升
|
||||
maxFileSizeMB!: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(20)
|
||||
maxFiles!: number;
|
||||
|
||||
@IsArray()
|
||||
@IsIn(['image', 'video', 'audio', 'document', 'archive', 'other'], {
|
||||
each: true,
|
||||
})
|
||||
allowedTypes!: string[];
|
||||
}
|
||||
|
||||
export interface UploadSettingsVo {
|
||||
maxFileSizeMB: number;
|
||||
maxFiles: number;
|
||||
allowedTypes: string[];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UploadSettingsService } from './upload-settings.service';
|
||||
import { UploadSettingsController } from './upload-settings.controller';
|
||||
|
||||
@Module({
|
||||
providers: [UploadSettingsService],
|
||||
controllers: [UploadSettingsController],
|
||||
exports: [UploadSettingsService],
|
||||
})
|
||||
export class UploadSettingsModule {}
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type { UploadSettingsVo } from './upload-settings.dto';
|
||||
|
||||
const SETTINGS_DIR = path.resolve(process.cwd(), 'config', 'runtime');
|
||||
const SETTINGS_FILE = path.resolve(SETTINGS_DIR, 'upload.settings.json');
|
||||
|
||||
const DEFAULT_SETTINGS: UploadSettingsVo = {
|
||||
maxFileSizeMB: 50,
|
||||
maxFiles: 10,
|
||||
allowedTypes: ['image', 'video', 'audio', 'document', 'archive', 'other'],
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class UploadSettingsService {
|
||||
async getSettings(): Promise<UploadSettingsVo> {
|
||||
try {
|
||||
const buf = await fs.promises.readFile(SETTINGS_FILE, 'utf8');
|
||||
const json = JSON.parse(buf);
|
||||
return { ...DEFAULT_SETTINGS, ...json };
|
||||
} catch {
|
||||
return { ...DEFAULT_SETTINGS };
|
||||
}
|
||||
}
|
||||
|
||||
async updateSettings(
|
||||
patch: Partial<UploadSettingsVo>,
|
||||
): Promise<UploadSettingsVo> {
|
||||
const current = await this.getSettings();
|
||||
const next: UploadSettingsVo = { ...current, ...patch };
|
||||
await fs.promises.mkdir(SETTINGS_DIR, { recursive: true });
|
||||
await fs.promises.writeFile(
|
||||
SETTINGS_FILE,
|
||||
JSON.stringify(next, null, 2),
|
||||
'utf8',
|
||||
);
|
||||
return next;
|
||||
}
|
||||
|
||||
static getSettingsPath() {
|
||||
return SETTINGS_FILE;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user