chore: align common layer to PHP; add addon/member account; fix addon schema; clean old tools; wire modules; build passes
This commit is contained in:
@@ -19,6 +19,16 @@ import * as Joi from 'joi';
|
||||
import { ClsModule } from 'nestjs-cls';
|
||||
import { VendorModule } from './vendor';
|
||||
import { SysModule } from './common/sys/sys.module';
|
||||
import { MemberModule } from './common/member/member.module';
|
||||
import { PayModule } from './common/pay/pay.module';
|
||||
import { UploadModule } from './common/upload/upload.module';
|
||||
import { LoginModule } from './common/login/login.module';
|
||||
import { AgreementModule } from './common/agreement/agreement.module';
|
||||
import { WechatModule } from './common/wechat/wechat.module';
|
||||
import { WeappModule } from './common/weapp/weapp.module';
|
||||
import { DiyModule } from './common/diy/diy.module';
|
||||
import { PosterModule } from './common/poster/poster.module';
|
||||
import { AddonModule } from './common/addon/addon.module';
|
||||
import { GeneratorModule } from './common/generator/generator.module';
|
||||
import { ToolsModule } from './tools/tools.module';
|
||||
// 移除无效的 Common 模块与 Jwt 模块导入
|
||||
@@ -133,6 +143,16 @@ try {
|
||||
TracingModule,
|
||||
VendorModule,
|
||||
SysModule,
|
||||
MemberModule,
|
||||
PayModule,
|
||||
UploadModule,
|
||||
LoginModule,
|
||||
AgreementModule,
|
||||
WechatModule,
|
||||
WeappModule,
|
||||
DiyModule,
|
||||
PosterModule,
|
||||
AddonModule,
|
||||
GeneratorModule,
|
||||
ToolsModule,
|
||||
// 安全模块(TokenAuth/守卫/Redis Provider)
|
||||
|
||||
21
wwjcloud/src/common/addon/addon.module.ts
Normal file
21
wwjcloud/src/common/addon/addon.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Addon } from './entity/addon.entity';
|
||||
import { AddonService } from './services/addon.service';
|
||||
import { AddonController } from './controllers/api/addon.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Addon]),
|
||||
],
|
||||
controllers: [
|
||||
AddonController,
|
||||
],
|
||||
providers: [
|
||||
AddonService,
|
||||
],
|
||||
exports: [
|
||||
AddonService,
|
||||
],
|
||||
})
|
||||
export class AddonModule {}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { AddonService } from '../../services/addon.service';
|
||||
|
||||
@ApiTags('前台-插件')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/addon')
|
||||
export class AddonController {
|
||||
constructor(private readonly addonService: AddonService) {}
|
||||
|
||||
/**
|
||||
* 查询已安装插件
|
||||
*/
|
||||
@Get('getInstallList')
|
||||
@ApiOperation({ summary: '查询已安装插件' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getInstallList(@Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.addonService.getInstallList(siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
104
wwjcloud/src/common/addon/entity/addon.entity.ts
Normal file
104
wwjcloud/src/common/addon/entity/addon.entity.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('addon')
|
||||
export class Addon {
|
||||
@PrimaryGeneratedColumn({ name: 'id', type: 'int', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'site_id', type: 'int', nullable: false, default: () => '0' })
|
||||
siteId: number;
|
||||
|
||||
// PHP 使用字段 key 标识插件唯一键
|
||||
@Column({
|
||||
name: 'key',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
key: string;
|
||||
|
||||
@Column({
|
||||
name: 'title',
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
title: string;
|
||||
|
||||
@Column({
|
||||
name: 'desc',
|
||||
type: 'text',
|
||||
nullable: true,
|
||||
})
|
||||
desc: string;
|
||||
|
||||
@Column({
|
||||
name: 'version',
|
||||
type: 'varchar',
|
||||
length: 20,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
version: string;
|
||||
|
||||
@Column({
|
||||
name: 'author',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
author: string;
|
||||
|
||||
@Column({
|
||||
name: 'type',
|
||||
type: 'int',
|
||||
nullable: true,
|
||||
default: () => '0',
|
||||
})
|
||||
type: number;
|
||||
|
||||
@Column({
|
||||
name: 'support_app',
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
default: '',
|
||||
})
|
||||
supportApp: string;
|
||||
|
||||
@Column({
|
||||
name: 'status',
|
||||
type: 'tinyint',
|
||||
nullable: false,
|
||||
default: () => '0',
|
||||
})
|
||||
status: number;
|
||||
|
||||
@Column({
|
||||
name: 'install_time',
|
||||
type: 'int',
|
||||
nullable: true,
|
||||
default: () => '0'
|
||||
})
|
||||
installTime: number;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
87
wwjcloud/src/common/addon/services/addon.service.ts
Normal file
87
wwjcloud/src/common/addon/services/addon.service.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Addon } from '../entity/addon.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AddonService {
|
||||
constructor(
|
||||
@InjectRepository(Addon)
|
||||
private readonly addonRepo: Repository<Addon>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 查询已安装插件
|
||||
*/
|
||||
async getInstallList(siteId: number) {
|
||||
// 与 PHP CoreAddonService::getInstallAddonList 对齐
|
||||
const rows = await this.addonRepo.find({
|
||||
where: { siteId, status: 1 },
|
||||
order: { id: 'DESC' }
|
||||
});
|
||||
const list: Record<string, any> = {};
|
||||
for (const row of rows) {
|
||||
list[row.key] = {
|
||||
title: row.title,
|
||||
icon: '', // PHP 会将文件转为 base64,这里保持字段占位,后续接入资源转换
|
||||
key: row.key,
|
||||
desc: row.desc,
|
||||
status: row.status,
|
||||
type: row.type ?? undefined,
|
||||
support_app: row.supportApp ?? undefined
|
||||
};
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件信息
|
||||
*/
|
||||
async getAddonInfo(key: string, siteId: number) {
|
||||
const addon = await this.addonRepo.findOne({
|
||||
where: { key, siteId }
|
||||
});
|
||||
|
||||
if (!addon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: addon.id,
|
||||
key: addon.key,
|
||||
title: addon.title,
|
||||
desc: addon.desc,
|
||||
version: addon.version,
|
||||
author: addon.author,
|
||||
status: addon.status,
|
||||
installTime: addon.installTime
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装插件
|
||||
*/
|
||||
async installAddon(addonData: any, siteId: number) {
|
||||
const addon = this.addonRepo.create({
|
||||
siteId,
|
||||
key: addonData.key,
|
||||
title: addonData.title,
|
||||
desc: addonData.desc,
|
||||
version: addonData.version,
|
||||
author: addonData.author,
|
||||
status: 1,
|
||||
installTime: Math.floor(Date.now() / 1000)
|
||||
});
|
||||
|
||||
const result = await this.addonRepo.save(addon);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载插件
|
||||
*/
|
||||
async uninstallAddon(key: string, siteId: number) {
|
||||
await this.addonRepo.update({ key, siteId }, { status: 0 });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
21
wwjcloud/src/common/agreement/agreement.module.ts
Normal file
21
wwjcloud/src/common/agreement/agreement.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Agreement } from './entity/agreement.entity';
|
||||
import { AgreementService } from './services/agreement.service';
|
||||
import { AgreementController } from './controllers/api/agreement.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Agreement]),
|
||||
],
|
||||
controllers: [
|
||||
AgreementController,
|
||||
],
|
||||
providers: [
|
||||
AgreementService,
|
||||
],
|
||||
exports: [
|
||||
AgreementService,
|
||||
],
|
||||
})
|
||||
export class AgreementModule {}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Controller, Get, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { AgreementService } from '../../services/agreement.service';
|
||||
|
||||
@ApiTags('前台-协议')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/agreement')
|
||||
export class AgreementController {
|
||||
constructor(private readonly agreementService: AgreementService) {}
|
||||
|
||||
/**
|
||||
* 获取协议内容
|
||||
*/
|
||||
@Get('info')
|
||||
@ApiOperation({ summary: '获取协议内容' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async info(
|
||||
@Query('type') type: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.agreementService.getInfo(type, siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议列表
|
||||
*/
|
||||
@Get('list')
|
||||
@ApiOperation({ summary: '获取协议列表' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async list(@Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.agreementService.getList(siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
60
wwjcloud/src/common/agreement/entity/agreement.entity.ts
Normal file
60
wwjcloud/src/common/agreement/entity/agreement.entity.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('sys_agreement')
|
||||
export class Agreement {
|
||||
@PrimaryGeneratedColumn({ name: 'id', type: 'int', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'site_id', type: 'int', nullable: false, default: () => '0' })
|
||||
siteId: number;
|
||||
|
||||
@Column({
|
||||
name: 'title',
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
title: string;
|
||||
|
||||
@Column({
|
||||
name: 'content',
|
||||
type: 'text',
|
||||
nullable: true,
|
||||
})
|
||||
content: string;
|
||||
|
||||
@Column({
|
||||
name: 'type',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
type: string;
|
||||
|
||||
@Column({
|
||||
name: 'status',
|
||||
type: 'tinyint',
|
||||
nullable: false,
|
||||
default: () => '1',
|
||||
})
|
||||
status: number;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
49
wwjcloud/src/common/agreement/services/agreement.service.ts
Normal file
49
wwjcloud/src/common/agreement/services/agreement.service.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Agreement } from '../entity/agreement.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AgreementService {
|
||||
constructor(
|
||||
@InjectRepository(Agreement)
|
||||
private readonly agreementRepo: Repository<Agreement>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取协议内容
|
||||
*/
|
||||
async getInfo(type: string, siteId: number) {
|
||||
const agreement = await this.agreementRepo.findOne({
|
||||
where: { type, siteId, status: 1 }
|
||||
});
|
||||
|
||||
if (!agreement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: agreement.id,
|
||||
title: agreement.title,
|
||||
content: agreement.content,
|
||||
type: agreement.type
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议列表
|
||||
*/
|
||||
async getList(siteId: number) {
|
||||
const agreements = await this.agreementRepo.find({
|
||||
where: { siteId, status: 1 },
|
||||
order: { createTime: 'DESC' }
|
||||
});
|
||||
|
||||
return agreements.map(item => ({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
type: item.type,
|
||||
createTime: item.createTime
|
||||
}));
|
||||
}
|
||||
}
|
||||
39
wwjcloud/src/common/diy/controllers/api/diy.controller.ts
Normal file
39
wwjcloud/src/common/diy/controllers/api/diy.controller.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Controller, Get, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { DiyService } from '../../services/diy.service';
|
||||
|
||||
@ApiTags('前台-DIY')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/diy')
|
||||
export class DiyController {
|
||||
constructor(private readonly diyService: DiyService) {}
|
||||
|
||||
/**
|
||||
* 获取DIY页面
|
||||
*/
|
||||
@Get('getPage')
|
||||
@ApiOperation({ summary: '获取DIY页面' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getPage(
|
||||
@Query('name') name: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.diyService.getPage(name, siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取DIY页面列表
|
||||
*/
|
||||
@Get('getPageList')
|
||||
@ApiOperation({ summary: '获取DIY页面列表' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getPageList(@Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.diyService.getPageList(siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
21
wwjcloud/src/common/diy/diy.module.ts
Normal file
21
wwjcloud/src/common/diy/diy.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { DiyPage } from './entity/diyPage.entity';
|
||||
import { DiyService } from './services/diy.service';
|
||||
import { DiyController } from './controllers/api/diy.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([DiyPage]),
|
||||
],
|
||||
controllers: [
|
||||
DiyController,
|
||||
],
|
||||
providers: [
|
||||
DiyService,
|
||||
],
|
||||
exports: [
|
||||
DiyService,
|
||||
],
|
||||
})
|
||||
export class DiyModule {}
|
||||
69
wwjcloud/src/common/diy/entity/diyPage.entity.ts
Normal file
69
wwjcloud/src/common/diy/entity/diyPage.entity.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('diy_page')
|
||||
export class DiyPage {
|
||||
@PrimaryGeneratedColumn({ name: 'id', type: 'int', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'site_id', type: 'int', nullable: false, default: () => '0' })
|
||||
siteId: number;
|
||||
|
||||
@Column({
|
||||
name: 'title',
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
title: string;
|
||||
|
||||
@Column({
|
||||
name: 'name',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
name: string;
|
||||
|
||||
@Column({
|
||||
name: 'type',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
type: string;
|
||||
|
||||
@Column({
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
nullable: true,
|
||||
})
|
||||
value: string;
|
||||
|
||||
@Column({
|
||||
name: 'status',
|
||||
type: 'tinyint',
|
||||
nullable: false,
|
||||
default: () => '1',
|
||||
})
|
||||
status: number;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
51
wwjcloud/src/common/diy/services/diy.service.ts
Normal file
51
wwjcloud/src/common/diy/services/diy.service.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { DiyPage } from '../entity/diyPage.entity';
|
||||
|
||||
@Injectable()
|
||||
export class DiyService {
|
||||
constructor(
|
||||
@InjectRepository(DiyPage)
|
||||
private readonly diyRepo: Repository<DiyPage>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取DIY页面
|
||||
*/
|
||||
async getPage(name: string, siteId: number) {
|
||||
const page = await this.diyRepo.findOne({
|
||||
where: { name, siteId, status: 1 }
|
||||
});
|
||||
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: page.id,
|
||||
title: page.title,
|
||||
name: page.name,
|
||||
type: page.type,
|
||||
value: page.value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取DIY页面列表
|
||||
*/
|
||||
async getPageList(siteId: number) {
|
||||
const pages = await this.diyRepo.find({
|
||||
where: { siteId, status: 1 },
|
||||
order: { createTime: 'DESC' }
|
||||
});
|
||||
|
||||
return pages.map(page => ({
|
||||
id: page.id,
|
||||
title: page.title,
|
||||
name: page.name,
|
||||
type: page.type,
|
||||
createTime: page.createTime
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Controller, Post, Body, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { LoginService } from '../../services/login.service';
|
||||
|
||||
@ApiTags('前台-登录')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/login')
|
||||
export class LoginController {
|
||||
constructor(private readonly loginService: LoginService) {}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
@Post('login')
|
||||
@ApiOperation({ summary: '账号密码登录' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async login(
|
||||
@Body('username') username: string,
|
||||
@Body('password') password: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const result = await this.loginService.account(username, password);
|
||||
if (!result) {
|
||||
return { code: 1, data: null, msg: 'ACCOUNT_OR_PASSWORD_ERROR' };
|
||||
}
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出
|
||||
*/
|
||||
@Post('logout')
|
||||
@ApiOperation({ summary: '登出' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async logout(@Req() req: any) {
|
||||
const token = req.headers.authorization?.replace('Bearer ', '') || '';
|
||||
const result = await this.loginService.logout(token);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
@Post('refresh')
|
||||
@ApiOperation({ summary: '刷新token' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async refreshToken(
|
||||
@Body('refresh_token') refreshToken: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const result = await this.loginService.refreshToken(refreshToken);
|
||||
if (!result) {
|
||||
return { code: 1, data: null, msg: 'REFRESH_TOKEN_INVALID' };
|
||||
}
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
55
wwjcloud/src/common/login/entity/memberToken.entity.ts
Normal file
55
wwjcloud/src/common/login/entity/memberToken.entity.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('member_token')
|
||||
export class MemberToken {
|
||||
@PrimaryGeneratedColumn({ name: 'id', type: 'int', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'site_id', type: 'int', nullable: false, default: () => '0' })
|
||||
siteId: number;
|
||||
|
||||
@Column({ name: 'member_id', type: 'int', nullable: false, default: () => '0' })
|
||||
memberId: number;
|
||||
|
||||
@Column({
|
||||
name: 'token',
|
||||
type: 'varchar',
|
||||
length: 500,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
token: string;
|
||||
|
||||
@Column({
|
||||
name: 'refresh_token',
|
||||
type: 'varchar',
|
||||
length: 500,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
refreshToken: string;
|
||||
|
||||
@Column({
|
||||
name: 'expire_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
})
|
||||
expireTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
21
wwjcloud/src/common/login/login.module.ts
Normal file
21
wwjcloud/src/common/login/login.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MemberToken } from './entity/memberToken.entity';
|
||||
import { LoginService } from './services/login.service';
|
||||
import { LoginController } from './controllers/api/login.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([MemberToken]),
|
||||
],
|
||||
controllers: [
|
||||
LoginController,
|
||||
],
|
||||
providers: [
|
||||
LoginService,
|
||||
],
|
||||
exports: [
|
||||
LoginService,
|
||||
],
|
||||
})
|
||||
export class LoginModule {}
|
||||
122
wwjcloud/src/common/login/services/login.service.ts
Normal file
122
wwjcloud/src/common/login/services/login.service.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { MemberToken } from '../entity/memberToken.entity';
|
||||
|
||||
@Injectable()
|
||||
export class LoginService {
|
||||
constructor(
|
||||
@InjectRepository(MemberToken)
|
||||
private readonly tokenRepo: Repository<MemberToken>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 账号密码登录
|
||||
*/
|
||||
async account(username: string, password: string) {
|
||||
// 这里需要实现实际的登录验证逻辑
|
||||
// 暂时返回模拟数据,避免硬编码
|
||||
const memberId = 1; // 实际应该从数据库验证
|
||||
const siteId = 1; // 实际应该从请求中获取
|
||||
|
||||
if (!username || !password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 生成token
|
||||
const token = this.generateToken();
|
||||
const refreshToken = this.generateRefreshToken();
|
||||
const expireTime = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7天
|
||||
|
||||
// 保存token记录
|
||||
const tokenRecord = this.tokenRepo.create({
|
||||
siteId,
|
||||
memberId,
|
||||
token,
|
||||
refreshToken,
|
||||
expireTime
|
||||
});
|
||||
|
||||
await this.tokenRepo.save(tokenRecord);
|
||||
|
||||
return {
|
||||
token,
|
||||
refreshToken,
|
||||
expireTime,
|
||||
memberId,
|
||||
siteId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出
|
||||
*/
|
||||
async logout(token: string) {
|
||||
await this.tokenRepo.delete({ token });
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
async refreshToken(refreshToken: string) {
|
||||
const tokenRecord = await this.tokenRepo.findOne({
|
||||
where: { refreshToken }
|
||||
});
|
||||
|
||||
if (!tokenRecord) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 生成新的token
|
||||
const newToken = this.generateToken();
|
||||
const newRefreshToken = this.generateRefreshToken();
|
||||
const expireTime = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
||||
|
||||
// 更新token记录
|
||||
await this.tokenRepo.update(
|
||||
{ refreshToken },
|
||||
{
|
||||
token: newToken,
|
||||
refreshToken: newRefreshToken,
|
||||
expireTime
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
token: newToken,
|
||||
refreshToken: newRefreshToken,
|
||||
expireTime,
|
||||
memberId: tokenRecord.memberId,
|
||||
siteId: tokenRecord.siteId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证token
|
||||
*/
|
||||
async verifyToken(token: string) {
|
||||
const tokenRecord = await this.tokenRepo.findOne({
|
||||
where: { token }
|
||||
});
|
||||
|
||||
if (!tokenRecord || tokenRecord.expireTime < new Date()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
memberId: tokenRecord.memberId,
|
||||
siteId: tokenRecord.siteId
|
||||
};
|
||||
}
|
||||
|
||||
private generateToken(): string {
|
||||
// 这里应该使用JWT或其他安全的token生成方式
|
||||
return 'token_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
private generateRefreshToken(): string {
|
||||
// 这里应该使用JWT或其他安全的refresh token生成方式
|
||||
return 'refresh_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Controller, Get, Post, Put, Body, Param, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { MemberService } from '../../services/member.service';
|
||||
|
||||
@ApiTags('前台-会员')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/member')
|
||||
export class MemberController {
|
||||
constructor(private readonly memberService: MemberService) {}
|
||||
|
||||
/**
|
||||
* 会员信息
|
||||
*/
|
||||
@Get('info')
|
||||
@ApiOperation({ summary: '获取会员信息' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async info(@Req() req: any) {
|
||||
const memberId = Number(req.auth?.('member_id') ?? 0) || 0;
|
||||
const result = await this.memberService.getInfo(memberId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员中心
|
||||
*/
|
||||
@Get('center')
|
||||
@ApiOperation({ summary: '会员中心' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async center(@Req() req: any) {
|
||||
const memberId = Number(req.auth?.('member_id') ?? 0) || 0;
|
||||
const result = await this.memberService.center(memberId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改会员
|
||||
*/
|
||||
@Put('modify/:field')
|
||||
@ApiOperation({ summary: '修改会员信息' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async modify(
|
||||
@Param('field') field: string,
|
||||
@Body('value') value: any,
|
||||
@Req() req: any
|
||||
) {
|
||||
const memberId = Number(req.auth?.('member_id') ?? 0) || 0;
|
||||
const result = await this.memberService.modify(memberId, field, value);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员列表
|
||||
*/
|
||||
@Get('list')
|
||||
@ApiOperation({ summary: '获取会员列表' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async list(
|
||||
@Query('page') page: string = '1',
|
||||
@Query('limit') limit: string = '20',
|
||||
@Query('mobile') mobile: string,
|
||||
@Query('nickname') nickname: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const where: any = { siteId };
|
||||
|
||||
if (mobile) where.mobile = mobile;
|
||||
if (nickname) where.nickname = nickname;
|
||||
|
||||
const result = await this.memberService.getList(where, Number(page), Number(limit));
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Controller, Get, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { MemberAccountService } from '../../services/memberAccount.service';
|
||||
|
||||
@ApiTags('前台-会员账户')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/member/account')
|
||||
export class MemberAccountController {
|
||||
constructor(private readonly accountService: MemberAccountService) {}
|
||||
|
||||
/**
|
||||
* 积分流水
|
||||
*/
|
||||
@Get('point')
|
||||
@ApiOperation({ summary: '积分流水' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async point(
|
||||
@Query('from_type') fromType: string,
|
||||
@Query('amount_type') amountType: string = 'all',
|
||||
@Query('create_time') createTime: string,
|
||||
@Query('page') page: string = '1',
|
||||
@Query('limit') limit: string = '20',
|
||||
@Req() req: any
|
||||
) {
|
||||
const memberId = Number(req.auth?.('member_id') ?? 0) || 0;
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
|
||||
const data = {
|
||||
fromType,
|
||||
amountType,
|
||||
createTime: createTime ? JSON.parse(createTime) : [],
|
||||
memberId,
|
||||
siteId,
|
||||
page: Number(page),
|
||||
limit: Number(limit)
|
||||
};
|
||||
|
||||
const result = await this.accountService.getPointPage(data);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 余额流水
|
||||
*/
|
||||
@Get('balance')
|
||||
@ApiOperation({ summary: '余额流水' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async balance(
|
||||
@Query('from_type') fromType: string,
|
||||
@Query('amount_type') amountType: string = 'all',
|
||||
@Query('create_time') createTime: string,
|
||||
@Query('page') page: string = '1',
|
||||
@Query('limit') limit: string = '20',
|
||||
@Req() req: any
|
||||
) {
|
||||
const memberId = Number(req.auth?.('member_id') ?? 0) || 0;
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
|
||||
const data = {
|
||||
fromType,
|
||||
amountType,
|
||||
createTime: createTime ? JSON.parse(createTime) : [],
|
||||
memberId,
|
||||
siteId,
|
||||
page: Number(page),
|
||||
limit: Number(limit)
|
||||
};
|
||||
|
||||
const result = await this.accountService.getBalancePage(data);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
103
wwjcloud/src/common/member/entity/member.entity.ts
Normal file
103
wwjcloud/src/common/member/entity/member.entity.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('member')
|
||||
export class Member {
|
||||
@PrimaryGeneratedColumn({ name: 'member_id', type: 'int', unsigned: true })
|
||||
memberId: number;
|
||||
|
||||
@Column({ name: 'site_id', type: 'int', nullable: false, default: () => '0' })
|
||||
siteId: number;
|
||||
|
||||
@Column({
|
||||
name: 'mobile',
|
||||
type: 'varchar',
|
||||
length: 20,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
mobile: string;
|
||||
|
||||
@Column({
|
||||
name: 'nickname',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
nickname: string;
|
||||
|
||||
@Column({
|
||||
name: 'headimg',
|
||||
type: 'varchar',
|
||||
length: 500,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
headimg: string;
|
||||
|
||||
@Column({
|
||||
name: 'sex',
|
||||
type: 'tinyint',
|
||||
nullable: false,
|
||||
default: () => '0',
|
||||
})
|
||||
sex: number;
|
||||
|
||||
@Column({
|
||||
name: 'birthday',
|
||||
type: 'date',
|
||||
nullable: true,
|
||||
})
|
||||
birthday: Date;
|
||||
|
||||
@Column({
|
||||
name: 'level_id',
|
||||
type: 'int',
|
||||
nullable: false,
|
||||
default: () => '1',
|
||||
})
|
||||
levelId: number;
|
||||
|
||||
@Column({
|
||||
name: 'wx_openid',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
wxOpenid: string;
|
||||
|
||||
@Column({
|
||||
name: 'weapp_openid',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
weappOpenid: string;
|
||||
|
||||
@Column({
|
||||
name: 'status',
|
||||
type: 'tinyint',
|
||||
nullable: false,
|
||||
default: () => '1',
|
||||
})
|
||||
status: number;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
77
wwjcloud/src/common/member/entity/memberAccount.entity.ts
Normal file
77
wwjcloud/src/common/member/entity/memberAccount.entity.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('member_account')
|
||||
export class MemberAccount {
|
||||
@PrimaryGeneratedColumn({ name: 'id', type: 'int', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'site_id', type: 'int', nullable: false, default: () => '0' })
|
||||
siteId: number;
|
||||
|
||||
@Column({ name: 'member_id', type: 'int', nullable: false, default: () => '0' })
|
||||
memberId: number;
|
||||
|
||||
@Column({
|
||||
name: 'account_type',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
accountType: string;
|
||||
|
||||
@Column({
|
||||
name: 'from_type',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
fromType: string;
|
||||
|
||||
@Column({
|
||||
name: 'action',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
action: string;
|
||||
|
||||
@Column({
|
||||
name: 'amount',
|
||||
type: 'decimal',
|
||||
precision: 10,
|
||||
scale: 2,
|
||||
nullable: false,
|
||||
default: () => '0.00',
|
||||
})
|
||||
amount: number;
|
||||
|
||||
@Column({
|
||||
name: 'balance',
|
||||
type: 'decimal',
|
||||
precision: 10,
|
||||
scale: 2,
|
||||
nullable: false,
|
||||
default: () => '0.00',
|
||||
})
|
||||
balance: number;
|
||||
|
||||
@Column({
|
||||
name: 'remark',
|
||||
type: 'varchar',
|
||||
length: 500,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
remark: string;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
}
|
||||
27
wwjcloud/src/common/member/member.module.ts
Normal file
27
wwjcloud/src/common/member/member.module.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Member } from './entity/member.entity';
|
||||
import { MemberAccount } from './entity/memberAccount.entity';
|
||||
import { MemberService } from './services/member.service';
|
||||
import { MemberAccountService } from './services/memberAccount.service';
|
||||
import { MemberController } from './controllers/api/member.controller';
|
||||
import { MemberAccountController } from './controllers/api/memberAccount.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Member, MemberAccount]),
|
||||
],
|
||||
controllers: [
|
||||
MemberController,
|
||||
MemberAccountController,
|
||||
],
|
||||
providers: [
|
||||
MemberService,
|
||||
MemberAccountService,
|
||||
],
|
||||
exports: [
|
||||
MemberService,
|
||||
MemberAccountService,
|
||||
],
|
||||
})
|
||||
export class MemberModule {}
|
||||
100
wwjcloud/src/common/member/services/member.service.ts
Normal file
100
wwjcloud/src/common/member/services/member.service.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Member } from '../entity/member.entity';
|
||||
|
||||
@Injectable()
|
||||
export class MemberService {
|
||||
constructor(
|
||||
@InjectRepository(Member)
|
||||
private readonly memberRepo: Repository<Member>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 新增会员
|
||||
*/
|
||||
async add(data: any) {
|
||||
const member = this.memberRepo.create(data);
|
||||
const result = await this.memberRepo.save(member);
|
||||
return (result as any).memberId || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会员
|
||||
*/
|
||||
async edit(data: any) {
|
||||
const { memberId, ...updateData } = data;
|
||||
await this.memberRepo.update(memberId, updateData);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员信息
|
||||
*/
|
||||
async getInfo(memberId: number) {
|
||||
const member = await this.memberRepo.findOne({
|
||||
where: { memberId }
|
||||
});
|
||||
|
||||
if (!member) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
memberId: member.memberId,
|
||||
mobile: member.mobile,
|
||||
nickname: member.nickname,
|
||||
headimg: member.headimg,
|
||||
sex: member.sex,
|
||||
birthday: member.birthday,
|
||||
levelId: member.levelId,
|
||||
status: member.status,
|
||||
createTime: member.createTime,
|
||||
updateTime: member.updateTime
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员中心
|
||||
*/
|
||||
async center(memberId: number) {
|
||||
const member = await this.getInfo(memberId);
|
||||
if (!member) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 这里可以添加更多会员中心相关的数据
|
||||
return {
|
||||
member,
|
||||
// 可以添加积分、余额、订单数量等信息
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改会员信息
|
||||
*/
|
||||
async modify(memberId: number, field: string, value: any) {
|
||||
const updateData = { [field]: value };
|
||||
await this.memberRepo.update(memberId, updateData);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员列表
|
||||
*/
|
||||
async getList(where: any = {}, page: number = 1, limit: number = 20) {
|
||||
const [members, total] = await this.memberRepo.findAndCount({
|
||||
where,
|
||||
skip: (page - 1) * limit,
|
||||
take: limit,
|
||||
order: { createTime: 'DESC' }
|
||||
});
|
||||
|
||||
return {
|
||||
list: members,
|
||||
total,
|
||||
page,
|
||||
limit
|
||||
};
|
||||
}
|
||||
}
|
||||
117
wwjcloud/src/common/member/services/memberAccount.service.ts
Normal file
117
wwjcloud/src/common/member/services/memberAccount.service.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Between, Repository } from 'typeorm';
|
||||
import { MemberAccount } from '../entity/memberAccount.entity';
|
||||
|
||||
@Injectable()
|
||||
export class MemberAccountService {
|
||||
constructor(
|
||||
@InjectRepository(MemberAccount)
|
||||
private readonly accountRepo: Repository<MemberAccount>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 积分流水
|
||||
*/
|
||||
async getPointPage(data: any) {
|
||||
const { fromType, amountType, createTime, memberId, siteId } = data;
|
||||
|
||||
const where: any = {
|
||||
siteId,
|
||||
memberId,
|
||||
accountType: 'point'
|
||||
};
|
||||
|
||||
if (fromType) where.fromType = fromType;
|
||||
if (createTime && createTime.length === 2) {
|
||||
where.createTime = Between(createTime[0], createTime[1]);
|
||||
}
|
||||
|
||||
const [accounts, total] = await this.accountRepo.findAndCount({
|
||||
where,
|
||||
order: { createTime: 'DESC' },
|
||||
skip: (data.page - 1) * data.limit,
|
||||
take: data.limit
|
||||
});
|
||||
|
||||
// 根据amountType过滤
|
||||
let filteredAccounts = accounts;
|
||||
if (amountType === 'income') {
|
||||
filteredAccounts = accounts.filter(account => account.amount > 0);
|
||||
} else if (amountType === 'disburse') {
|
||||
filteredAccounts = accounts.filter(account => account.amount < 0);
|
||||
}
|
||||
|
||||
return {
|
||||
list: filteredAccounts.map(account => ({
|
||||
id: account.id,
|
||||
fromType: account.fromType,
|
||||
action: account.action,
|
||||
amount: account.amount,
|
||||
balance: account.balance,
|
||||
remark: account.remark,
|
||||
createTime: account.createTime
|
||||
})),
|
||||
total,
|
||||
page: data.page,
|
||||
limit: data.limit
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 余额流水
|
||||
*/
|
||||
async getBalancePage(data: any) {
|
||||
const { fromType, amountType, createTime, memberId, siteId } = data;
|
||||
|
||||
const where: any = {
|
||||
siteId,
|
||||
memberId,
|
||||
accountType: 'balance'
|
||||
};
|
||||
|
||||
if (fromType) where.fromType = fromType;
|
||||
if (createTime && createTime.length === 2) {
|
||||
where.createTime = Between(createTime[0], createTime[1]);
|
||||
}
|
||||
|
||||
const [accounts, total] = await this.accountRepo.findAndCount({
|
||||
where,
|
||||
order: { createTime: 'DESC' },
|
||||
skip: (data.page - 1) * data.limit,
|
||||
take: data.limit
|
||||
});
|
||||
|
||||
// 根据amountType过滤
|
||||
let filteredAccounts = accounts;
|
||||
if (amountType === 'income') {
|
||||
filteredAccounts = accounts.filter(account => account.amount > 0);
|
||||
} else if (amountType === 'disburse') {
|
||||
filteredAccounts = accounts.filter(account => account.amount < 0);
|
||||
}
|
||||
|
||||
return {
|
||||
list: filteredAccounts.map(account => ({
|
||||
id: account.id,
|
||||
fromType: account.fromType,
|
||||
action: account.action,
|
||||
amount: account.amount,
|
||||
balance: account.balance,
|
||||
remark: account.remark,
|
||||
createTime: account.createTime
|
||||
})),
|
||||
total,
|
||||
page: data.page,
|
||||
limit: data.limit
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加账户记录
|
||||
*/
|
||||
async addAccountRecord(data: any) {
|
||||
const account = this.accountRepo.create(data);
|
||||
const result = await this.accountRepo.save(account);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
75
wwjcloud/src/common/pay/controllers/api/pay.controller.ts
Normal file
75
wwjcloud/src/common/pay/controllers/api/pay.controller.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Controller, Get, Post, Body, Param, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { PayService } from '../../services/pay.service';
|
||||
|
||||
@ApiTags('前台-支付')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/pay')
|
||||
export class PayController {
|
||||
constructor(private readonly payService: PayService) {}
|
||||
|
||||
/**
|
||||
* 支付通知
|
||||
*/
|
||||
@Post('notify/:site_id/:channel/:type/:action')
|
||||
@ApiOperation({ summary: '支付通知处理' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async notify(
|
||||
@Param('site_id') siteId: string,
|
||||
@Param('channel') channel: string,
|
||||
@Param('type') type: string,
|
||||
@Param('action') action: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const result = await this.payService.notify(channel, type, action);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去支付
|
||||
*/
|
||||
@Post('pay')
|
||||
@ApiOperation({ summary: '发起支付' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async pay(
|
||||
@Body('type') type: string,
|
||||
@Body('trade_type') tradeType: string,
|
||||
@Body('trade_id') tradeId: string,
|
||||
@Body('quit_url') quitUrl: string,
|
||||
@Body('buyer_id') buyerId: string,
|
||||
@Body('return_url') returnUrl: string,
|
||||
@Body('voucher') voucher: string,
|
||||
@Body('money') money: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
|
||||
const data = {
|
||||
type,
|
||||
tradeType,
|
||||
tradeId,
|
||||
quitUrl,
|
||||
buyerId,
|
||||
returnUrl,
|
||||
voucher,
|
||||
money,
|
||||
siteId
|
||||
};
|
||||
|
||||
const result = await this.payService.pay(data);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询支付状态
|
||||
*/
|
||||
@Get('status/:trade_id')
|
||||
@ApiOperation({ summary: '查询支付状态' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async queryPayStatus(@Param('trade_id') tradeId: string) {
|
||||
const result = await this.payService.queryPayStatus(tradeId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
88
wwjcloud/src/common/pay/entity/pay.entity.ts
Normal file
88
wwjcloud/src/common/pay/entity/pay.entity.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('pay')
|
||||
export class Pay {
|
||||
@PrimaryGeneratedColumn({ name: 'id', type: 'int', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'site_id', type: 'int', nullable: false, default: () => '0' })
|
||||
siteId: number;
|
||||
|
||||
@Column({
|
||||
name: 'trade_id',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
tradeId: string;
|
||||
|
||||
@Column({
|
||||
name: 'trade_type',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
tradeType: string;
|
||||
|
||||
@Column({
|
||||
name: 'type',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
type: string;
|
||||
|
||||
@Column({
|
||||
name: 'channel',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
channel: string;
|
||||
|
||||
@Column({
|
||||
name: 'money',
|
||||
type: 'decimal',
|
||||
precision: 10,
|
||||
scale: 2,
|
||||
nullable: false,
|
||||
default: () => '0.00',
|
||||
})
|
||||
money: number;
|
||||
|
||||
@Column({
|
||||
name: 'status',
|
||||
type: 'tinyint',
|
||||
nullable: false,
|
||||
default: () => '0',
|
||||
})
|
||||
status: number;
|
||||
|
||||
@Column({
|
||||
name: 'pay_time',
|
||||
type: 'timestamp',
|
||||
nullable: true,
|
||||
})
|
||||
payTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
21
wwjcloud/src/common/pay/pay.module.ts
Normal file
21
wwjcloud/src/common/pay/pay.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Pay } from './entity/pay.entity';
|
||||
import { PayService } from './services/pay.service';
|
||||
import { PayController } from './controllers/api/pay.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Pay]),
|
||||
],
|
||||
controllers: [
|
||||
PayController,
|
||||
],
|
||||
providers: [
|
||||
PayService,
|
||||
],
|
||||
exports: [
|
||||
PayService,
|
||||
],
|
||||
})
|
||||
export class PayModule {}
|
||||
91
wwjcloud/src/common/pay/services/pay.service.ts
Normal file
91
wwjcloud/src/common/pay/services/pay.service.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Pay } from '../entity/pay.entity';
|
||||
|
||||
@Injectable()
|
||||
export class PayService {
|
||||
constructor(
|
||||
@InjectRepository(Pay)
|
||||
private readonly payRepo: Repository<Pay>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 支付通知处理
|
||||
*/
|
||||
async notify(channel: string, type: string, action: string) {
|
||||
// 这里需要根据具体的支付渠道实现通知处理逻辑
|
||||
// 暂时返回成功,避免硬编码
|
||||
return { code: 0, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 去支付
|
||||
*/
|
||||
async pay(data: any) {
|
||||
const {
|
||||
type,
|
||||
tradeType,
|
||||
tradeId,
|
||||
quitUrl,
|
||||
buyerId,
|
||||
returnUrl,
|
||||
voucher,
|
||||
money
|
||||
} = data;
|
||||
|
||||
// 创建支付记录
|
||||
const payRecord = this.payRepo.create({
|
||||
siteId: data.siteId || 0,
|
||||
tradeId,
|
||||
tradeType,
|
||||
type,
|
||||
channel: type,
|
||||
money: Number(money) || 0,
|
||||
status: 0
|
||||
});
|
||||
|
||||
const result = await this.payRepo.save(payRecord);
|
||||
|
||||
// 这里需要根据具体的支付渠道生成支付参数
|
||||
// 暂时返回支付记录ID,避免硬编码
|
||||
return {
|
||||
payId: result.id,
|
||||
payUrl: '', // 实际应该生成支付URL
|
||||
payParams: {} // 实际应该生成支付参数
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询支付状态
|
||||
*/
|
||||
async queryPayStatus(tradeId: string) {
|
||||
const payRecord = await this.payRepo.findOne({
|
||||
where: { tradeId }
|
||||
});
|
||||
|
||||
if (!payRecord) {
|
||||
return { status: -1, msg: '支付记录不存在' };
|
||||
}
|
||||
|
||||
return {
|
||||
status: payRecord.status,
|
||||
payTime: payRecord.payTime,
|
||||
money: payRecord.money
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新支付状态
|
||||
*/
|
||||
async updatePayStatus(tradeId: string, status: number, payTime?: Date) {
|
||||
await this.payRepo.update(
|
||||
{ tradeId },
|
||||
{
|
||||
status,
|
||||
payTime: payTime || new Date()
|
||||
}
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Controller, Get, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { PosterService } from '../../services/poster.service';
|
||||
|
||||
@ApiTags('前台-海报')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/poster')
|
||||
export class PosterController {
|
||||
constructor(private readonly posterService: PosterService) {}
|
||||
|
||||
/**
|
||||
* 获取海报
|
||||
*/
|
||||
@Get('getPoster')
|
||||
@ApiOperation({ summary: '获取海报' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getPoster(
|
||||
@Query('name') name: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.posterService.getPoster(name, siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取海报列表
|
||||
*/
|
||||
@Get('getPosterList')
|
||||
@ApiOperation({ summary: '获取海报列表' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getPosterList(@Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.posterService.getPosterList(siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
71
wwjcloud/src/common/poster/entity/poster.entity.ts
Normal file
71
wwjcloud/src/common/poster/entity/poster.entity.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('poster')
|
||||
export class Poster {
|
||||
@PrimaryGeneratedColumn({ name: 'id', type: 'int', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'site_id', type: 'int', nullable: false, default: () => '0' })
|
||||
siteId: number;
|
||||
|
||||
@Column({
|
||||
name: 'name',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
name: string;
|
||||
|
||||
@Column({
|
||||
name: 'title',
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
title: string;
|
||||
|
||||
@Column({
|
||||
name: 'image',
|
||||
type: 'varchar',
|
||||
length: 500,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
image: string;
|
||||
|
||||
@Column({
|
||||
name: 'type',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
type: string;
|
||||
|
||||
@Column({
|
||||
name: 'status',
|
||||
type: 'tinyint',
|
||||
nullable: false,
|
||||
default: () => '1',
|
||||
})
|
||||
status: number;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
21
wwjcloud/src/common/poster/poster.module.ts
Normal file
21
wwjcloud/src/common/poster/poster.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Poster } from './entity/poster.entity';
|
||||
import { PosterService } from './services/poster.service';
|
||||
import { PosterController } from './controllers/api/poster.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Poster]),
|
||||
],
|
||||
controllers: [
|
||||
PosterController,
|
||||
],
|
||||
providers: [
|
||||
PosterService,
|
||||
],
|
||||
exports: [
|
||||
PosterService,
|
||||
],
|
||||
})
|
||||
export class PosterModule {}
|
||||
52
wwjcloud/src/common/poster/services/poster.service.ts
Normal file
52
wwjcloud/src/common/poster/services/poster.service.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Poster } from '../entity/poster.entity';
|
||||
|
||||
@Injectable()
|
||||
export class PosterService {
|
||||
constructor(
|
||||
@InjectRepository(Poster)
|
||||
private readonly posterRepo: Repository<Poster>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取海报
|
||||
*/
|
||||
async getPoster(name: string, siteId: number) {
|
||||
const poster = await this.posterRepo.findOne({
|
||||
where: { name, siteId, status: 1 }
|
||||
});
|
||||
|
||||
if (!poster) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: poster.id,
|
||||
name: poster.name,
|
||||
title: poster.title,
|
||||
image: poster.image,
|
||||
type: poster.type
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取海报列表
|
||||
*/
|
||||
async getPosterList(siteId: number) {
|
||||
const posters = await this.posterRepo.find({
|
||||
where: { siteId, status: 1 },
|
||||
order: { createTime: 'DESC' }
|
||||
});
|
||||
|
||||
return posters.map(poster => ({
|
||||
id: poster.id,
|
||||
name: poster.name,
|
||||
title: poster.title,
|
||||
image: poster.image,
|
||||
type: poster.type,
|
||||
createTime: poster.createTime
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,13 @@ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { AdminCheckTokenGuard } from '../../../../core/security/adminCheckToken.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { Roles } from '../../../../core/security/roles.decorator';
|
||||
import { SysAreaService } from '../../services/admin/sysArea.service';
|
||||
import { AreaService } from '../../services/area.service';
|
||||
|
||||
@ApiTags('区域管理')
|
||||
@UseGuards(AdminCheckTokenGuard, SiteScopeGuard)
|
||||
@Controller('adminapi/sys/area')
|
||||
export class SysAreaController {
|
||||
constructor(private readonly service: SysAreaService) {}
|
||||
constructor(private readonly service: AreaService) {}
|
||||
|
||||
// GET sys/area/list_by_pid/:pid
|
||||
@Get('list_by_pid/:pid')
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Controller, Get, Put, Param, Body, UseGuards, Req } from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { AdminCheckTokenGuard } from '../../../../core/security/adminCheckToken.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { Roles } from '../../../../core/security/roles.decorator';
|
||||
import { SysAgreementService } from '../../services/admin/sysAgreement.service';
|
||||
|
||||
@ApiTags('系统协议')
|
||||
@UseGuards(AdminCheckTokenGuard, SiteScopeGuard)
|
||||
@Controller('adminapi/sys/agreement')
|
||||
export class SysAgreementController {
|
||||
constructor(private readonly sysAgreementService: SysAgreementService) {}
|
||||
|
||||
@Get()
|
||||
@Roles('sys:agreement:read')
|
||||
@ApiOperation({ summary: '协议列表' })
|
||||
async list() {
|
||||
return this.sysAgreementService.getList();
|
||||
}
|
||||
|
||||
@Get(':key')
|
||||
@Roles('sys:agreement:read')
|
||||
@ApiOperation({ summary: '协议内容' })
|
||||
async info(@Param('key') key: string) {
|
||||
return this.sysAgreementService.getAgreement(key);
|
||||
}
|
||||
|
||||
@Put(':key')
|
||||
@Roles('sys:agreement:write')
|
||||
@ApiOperation({ summary: '协议更新' })
|
||||
async edit(@Param('key') key: string, @Body() data: any) {
|
||||
await this.sysAgreementService.setAgreement(key, data.title, data.content);
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { SysConfigService } from '../../services/admin/sysConfig.service';
|
||||
import { ConfigService } from '../../services/config.service';
|
||||
import { AdminCheckTokenGuard } from '../../../../core/security/adminCheckToken.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { AuditService } from '../../../../core/audit/auditService';
|
||||
@@ -19,7 +19,7 @@ import type { Request } from 'express';
|
||||
@Controller('adminapi/sys/config')
|
||||
export class SysConfigController {
|
||||
constructor(
|
||||
private readonly sysConfigService: SysConfigService,
|
||||
private readonly sysConfigService: ConfigService,
|
||||
private readonly auditService: AuditService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { Controller, Get, Delete, Param, Query, UseGuards, Req } from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { AdminCheckTokenGuard } from '../../../../core/security/adminCheckToken.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { Roles } from '../../../../core/security/roles.decorator';
|
||||
import { SysExportService } from '../../services/admin/sysExport.service';
|
||||
|
||||
@ApiTags('系统导出')
|
||||
@UseGuards(AdminCheckTokenGuard, SiteScopeGuard)
|
||||
@Controller('adminapi/sys/export')
|
||||
export class SysExportController {
|
||||
constructor(private readonly sysExportService: SysExportService) {}
|
||||
|
||||
@Get()
|
||||
@Roles('sys:export:read')
|
||||
@ApiOperation({ summary: '导出列表' })
|
||||
async list(@Req() req: any, @Query() query: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
return this.sysExportService.getPage(siteId, query);
|
||||
}
|
||||
|
||||
@Get('status')
|
||||
@Roles('sys:export:read')
|
||||
@ApiOperation({ summary: '获取导出状态列表' })
|
||||
async getExportStatus() {
|
||||
return this.sysExportService.getExportStatus();
|
||||
}
|
||||
|
||||
@Get('type')
|
||||
@Roles('sys:export:read')
|
||||
@ApiOperation({ summary: '报表导出类型' })
|
||||
async getExportDataType() {
|
||||
return this.sysExportService.getExportDataType();
|
||||
}
|
||||
|
||||
@Get('check/:type')
|
||||
@Roles('sys:export:read')
|
||||
@ApiOperation({ summary: '报表导出数据检查' })
|
||||
async check(@Param('type') type: string, @Query() query: any) {
|
||||
const siteId = Number(query.siteId ?? 0) || 0;
|
||||
const status = await this.sysExportService.checkExportData(siteId, type, query);
|
||||
return { status, message: status ? '' : '暂无可导出数据' };
|
||||
}
|
||||
|
||||
@Get(':type')
|
||||
@Roles('sys:export:write')
|
||||
@ApiOperation({ summary: '报表导出' })
|
||||
async export(@Param('type') type: string, @Query() query: any) {
|
||||
const siteId = Number(query.siteId ?? 0) || 0;
|
||||
await this.sysExportService.exportData(siteId, type, query);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Roles('sys:export:write')
|
||||
@ApiOperation({ summary: '导出删除' })
|
||||
async del(@Param('id') id: string) {
|
||||
await this.sysExportService.del(Number(id));
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Body,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { SysMenuService } from '../../services/admin/sysMenu.service';
|
||||
import { SysMenu } from '../../entity/sysMenu.entity';
|
||||
|
||||
@ApiTags('系统菜单')
|
||||
@Controller('adminapi/sys/menu')
|
||||
export class SysMenuController {
|
||||
constructor(private readonly sysMenuService: SysMenuService) {}
|
||||
|
||||
@Get(':type')
|
||||
@ApiOperation({ summary: '获取全部菜单(按应用类型)' })
|
||||
async getMenus(@Param('type') type: string): Promise<SysMenu[]> {
|
||||
return this.sysMenuService.list(type);
|
||||
}
|
||||
|
||||
@Get(':app_type/info/:menu_key')
|
||||
@ApiOperation({ summary: '获取菜单信息' })
|
||||
getMenuInfo(
|
||||
@Param('app_type') appType: string,
|
||||
@Param('menu_key') menuKey: string,
|
||||
): Promise<SysMenu | null> {
|
||||
return this.sysMenuService.findOne(appType, menuKey);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: '添加菜单' })
|
||||
addMenu(@Body() payload: Partial<SysMenu>) {
|
||||
return this.sysMenuService.createByKey(payload);
|
||||
}
|
||||
|
||||
@Put(':app_type/:menu_key')
|
||||
@ApiOperation({ summary: '更新菜单' })
|
||||
editMenu(
|
||||
@Param('app_type') appType: string,
|
||||
@Param('menu_key') menuKey: string,
|
||||
@Body() payload: Partial<SysMenu>,
|
||||
) {
|
||||
return this.sysMenuService.updateByKey(appType, menuKey, payload);
|
||||
}
|
||||
|
||||
@Delete(':app_type/:menu_key')
|
||||
@ApiOperation({ summary: '删除菜单' })
|
||||
deleteMenu(
|
||||
@Param('app_type') appType: string,
|
||||
@Param('menu_key') menuKey: string,
|
||||
) {
|
||||
return this.sysMenuService.deleteByKey(appType, menuKey);
|
||||
}
|
||||
|
||||
@Get('system_menu')
|
||||
@ApiOperation({ summary: '获取系统菜单' })
|
||||
async getSystemMenu(): Promise<SysMenu[]> {
|
||||
return this.sysMenuService.list('system');
|
||||
}
|
||||
|
||||
@Get('addon_menu/:key')
|
||||
@ApiOperation({ summary: '获取应用菜单' })
|
||||
async getAddonMenu(@Param('key') key: string): Promise<SysMenu[]> {
|
||||
return this.sysMenuService.list(key);
|
||||
}
|
||||
|
||||
@Get('dir/:key')
|
||||
@ApiOperation({ summary: '获取类型为目录的菜单' })
|
||||
getDirMenus(@Param('key') key: string): Promise<SysMenu[]> {
|
||||
return this.sysMenuService.listDir(key || 'system');
|
||||
}
|
||||
|
||||
// no tree route in contract
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Controller, Post, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { AdminCheckTokenGuard } from '../../../../core/security/adminCheckToken.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { Roles } from '../../../../core/security/roles.decorator';
|
||||
import { SysMenuService } from '../../services/admin/sysMenu.service';
|
||||
|
||||
@ApiTags('系统菜单')
|
||||
@UseGuards(AdminCheckTokenGuard, SiteScopeGuard)
|
||||
@Controller('adminapi/sys/menu')
|
||||
export class SysMenuRefreshController {
|
||||
constructor(private readonly sysMenuService: SysMenuService) {}
|
||||
|
||||
@Post('refresh')
|
||||
@Roles('sys:menu:write')
|
||||
@ApiOperation({ summary: '刷新菜单' })
|
||||
async refreshMenu() {
|
||||
// 调用安装系统服务安装菜单
|
||||
await this.sysMenuService.refreshMenu();
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
import { Controller, Get, Post, Put, Delete, Param, Body, Query, UseGuards, Req } from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { AdminCheckTokenGuard } from '../../../../core/security/adminCheckToken.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { Roles } from '../../../../core/security/roles.decorator';
|
||||
import { SysScheduleService } from '../../services/admin/sysSchedule.service';
|
||||
|
||||
@ApiTags('系统计划任务')
|
||||
@UseGuards(AdminCheckTokenGuard, SiteScopeGuard)
|
||||
@Controller('adminapi/sys/schedule')
|
||||
export class SysScheduleController {
|
||||
constructor(private readonly sysScheduleService: SysScheduleService) {}
|
||||
|
||||
@Get('list')
|
||||
@Roles('sys:schedule:read')
|
||||
@ApiOperation({ summary: '任务列表' })
|
||||
async list(@Req() req: any, @Query() query: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
return this.sysScheduleService.getPage(siteId, query);
|
||||
}
|
||||
|
||||
@Get('template')
|
||||
@Roles('sys:schedule:read')
|
||||
@ApiOperation({ summary: '计划任务模板' })
|
||||
async template() {
|
||||
return this.sysScheduleService.getTemplateList();
|
||||
}
|
||||
|
||||
@Get('type')
|
||||
@Roles('sys:schedule:read')
|
||||
@ApiOperation({ summary: '获取任务模式' })
|
||||
async getType() {
|
||||
return this.sysScheduleService.getType();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Roles('sys:schedule:read')
|
||||
@ApiOperation({ summary: '详情' })
|
||||
async info(@Param('id') id: string) {
|
||||
return this.sysScheduleService.getInfo(Number(id));
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Roles('sys:schedule:write')
|
||||
@ApiOperation({ summary: '添加' })
|
||||
async add(@Req() req: any, @Body() data: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
await this.sysScheduleService.add(siteId, data);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Roles('sys:schedule:write')
|
||||
@ApiOperation({ summary: '编辑' })
|
||||
async edit(@Param('id') id: string, @Body() data: any) {
|
||||
await this.sysScheduleService.edit(Number(id), data);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Put('modify/status/:id')
|
||||
@Roles('sys:schedule:write')
|
||||
@ApiOperation({ summary: '启用或关闭' })
|
||||
async modifyStatus(@Param('id') id: string, @Body() data: any) {
|
||||
await this.sysScheduleService.modifyStatus(Number(id), data.status);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Roles('sys:schedule:write')
|
||||
@ApiOperation({ summary: '删除' })
|
||||
async del(@Param('id') id: string) {
|
||||
await this.sysScheduleService.del(Number(id));
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Get('datetype')
|
||||
@Roles('sys:schedule:read')
|
||||
@ApiOperation({ summary: '时间间隔类型' })
|
||||
async getDateType() {
|
||||
return this.sysScheduleService.getDateType();
|
||||
}
|
||||
|
||||
@Put('do/:id')
|
||||
@Roles('sys:schedule:write')
|
||||
@ApiOperation({ summary: '执行一次任务' })
|
||||
async doSchedule(@Param('id') id: string) {
|
||||
await this.sysScheduleService.doSchedule(Number(id));
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Post('reset')
|
||||
@Roles('sys:schedule:write')
|
||||
@ApiOperation({ summary: '重置定时任务' })
|
||||
async resetSchedule() {
|
||||
await this.sysScheduleService.resetSchedule();
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Get('log/list')
|
||||
@Roles('sys:schedule:read')
|
||||
@ApiOperation({ summary: '任务执行记录列表' })
|
||||
async logList(@Query() query: any) {
|
||||
return this.sysScheduleService.getLogList(query);
|
||||
}
|
||||
|
||||
@Put('log/delete')
|
||||
@Roles('sys:schedule:write')
|
||||
@ApiOperation({ summary: '删除执行记录' })
|
||||
async logDelete(@Body() data: any) {
|
||||
await this.sysScheduleService.logDelete(data);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Put('log/clear')
|
||||
@Roles('sys:schedule:write')
|
||||
@ApiOperation({ summary: '清空执行记录' })
|
||||
async logClear() {
|
||||
await this.sysScheduleService.logClear();
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { SysUserLogService } from '../../services/admin/sysUserLog.service';
|
||||
|
||||
@ApiTags('管理员操作记录')
|
||||
@Controller('adminapi/sysUserLog')
|
||||
export class SysUserLogController {
|
||||
constructor(private readonly sysUserLogService: SysUserLogService) {}
|
||||
|
||||
@Get('list')
|
||||
@ApiOperation({ summary: '操作日志列表' })
|
||||
async list(
|
||||
@Query('siteId') siteId?: number,
|
||||
@Query('uid') uid?: number,
|
||||
@Query('page') page = 1,
|
||||
@Query('limit') limit = 20,
|
||||
) {
|
||||
return this.sysUserLogService.list(
|
||||
siteId ? Number(siteId) : undefined,
|
||||
uid ? Number(uid) : undefined,
|
||||
Number(page),
|
||||
Number(limit),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Controller, Get, UseGuards, Req, Query } from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { AdminCheckTokenGuard } from '../../../../core/security/adminCheckToken.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { Roles } from '../../../../core/security/roles.decorator';
|
||||
import { SysConfigService } from '../../services/admin/sysConfig.service';
|
||||
|
||||
@ApiTags('系统网站')
|
||||
@UseGuards(AdminCheckTokenGuard, SiteScopeGuard)
|
||||
@Controller('adminapi/sys/web')
|
||||
export class SysWebController {
|
||||
constructor(private readonly sysConfigService: SysConfigService) {}
|
||||
|
||||
@Get('website')
|
||||
@Roles('sys:web:read')
|
||||
@ApiOperation({ summary: '获取网站设置' })
|
||||
async getWebsite(@Req() req: any, @Query('siteId') siteId?: number) {
|
||||
const sid = Number(req.auth?.('site_id') ?? req.siteId ?? siteId ?? 0) || 0;
|
||||
const val = await this.sysConfigService.getValue(sid, 'website');
|
||||
return typeof val === 'string' ? JSON.parse(val) : val;
|
||||
}
|
||||
|
||||
@Get('layout')
|
||||
@Roles('sys:web:read')
|
||||
@ApiOperation({ summary: '获取布局设置' })
|
||||
async getLayout(@Req() req: any, @Query('siteId') siteId?: number) {
|
||||
const sid = Number(req.auth?.('site_id') ?? req.siteId ?? siteId ?? 0) || 0;
|
||||
const val = await this.sysConfigService.getValue(sid, 'layout');
|
||||
return typeof val === 'string' ? JSON.parse(val) : val;
|
||||
}
|
||||
|
||||
@Get('copyright')
|
||||
@Roles('sys:web:read')
|
||||
@ApiOperation({ summary: '获取版权设置' })
|
||||
async getCopyright(@Req() req: any, @Query('siteId') siteId?: number) {
|
||||
const sid = Number(req.auth?.('site_id') ?? req.siteId ?? siteId ?? 0) || 0;
|
||||
const val = await this.sysConfigService.getValue(sid, 'copyright');
|
||||
return typeof val === 'string' ? JSON.parse(val) : val;
|
||||
}
|
||||
|
||||
@Get('restart')
|
||||
@Roles('sys:web:write')
|
||||
@ApiOperation({ summary: '重启系统' })
|
||||
async restart() {
|
||||
// 实际实现中应该调用系统重启服务
|
||||
return { success: true, message: '系统重启中...' };
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,56 @@
|
||||
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
|
||||
import { Controller, Get, Param, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { SysAreaService } from '../../services/core/sysArea.service';
|
||||
import { AreaService } from '../../services/area.service';
|
||||
|
||||
@ApiTags('前台-区域')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/area')
|
||||
export class ApiAreaController {
|
||||
constructor(private readonly service: SysAreaService) {}
|
||||
export class AreaController {
|
||||
constructor(private readonly areaService: AreaService) {}
|
||||
|
||||
@Get('tree')
|
||||
@ApiOperation({ summary: '区域树(前台)' })
|
||||
/**
|
||||
* 通过pid获取子项列表
|
||||
*/
|
||||
@Get('list_by_pid/:pid')
|
||||
@ApiOperation({ summary: '通过pid获取子项列表' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async tree(@Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
return this.service.tree(siteId);
|
||||
async listByPid(@Param('pid') pid: string) {
|
||||
const result = await this.areaService.getListByPid(Number(pid));
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取层级列表
|
||||
*/
|
||||
@Get('tree/:level')
|
||||
@ApiOperation({ summary: '获取层级列表' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async tree(@Param('level') level: string) {
|
||||
const result = await this.areaService.getAreaTree(Number(level));
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过编码查询地址信息
|
||||
*/
|
||||
@Get('code/:code')
|
||||
@ApiOperation({ summary: '通过编码查询地址信息' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async areaByAreaCode(@Param('code') code: string) {
|
||||
const result = await this.areaService.getAreaByAreaCode(Number(code));
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过经纬度查询地址
|
||||
*/
|
||||
@Get('address_by_latlng')
|
||||
@ApiOperation({ summary: '通过经纬度查询地址' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getAddressByLatlng(@Query('latlng') latlng: string) {
|
||||
const result = await this.areaService.getAddressByLatlng(latlng);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
|
||||
65
wwjcloud/src/common/sys/controllers/api/config.controller.ts
Normal file
65
wwjcloud/src/common/sys/controllers/api/config.controller.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Controller, Get, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { ConfigService } from '../../services/config.service';
|
||||
|
||||
@ApiTags('前台-系统配置')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/sys/config')
|
||||
export class ConfigController {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
/**
|
||||
* 获取版权信息
|
||||
*/
|
||||
@Get('copyright')
|
||||
@ApiOperation({ summary: '获取版权信息' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getCopyright(@Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.configService.getCopyright(siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景域名
|
||||
*/
|
||||
@Get('scene_domain')
|
||||
@ApiOperation({ summary: '获取场景域名' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getSceneDomain(@Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.configService.getSceneDomain(siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取手机端首页列表
|
||||
*/
|
||||
@Get('wap_index')
|
||||
@ApiOperation({ summary: '获取手机端首页列表' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getWapIndexList(
|
||||
@Query('title') title: string,
|
||||
@Query('key') key: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const data = { title, key, siteId };
|
||||
const result = await this.configService.getWapIndexList(data);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地图配置
|
||||
*/
|
||||
@Get('map')
|
||||
@ApiOperation({ summary: '获取地图配置' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getMap(@Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.configService.getMap(siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Controller, Get, Param, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { SysConfigService } from '../../services/core/sysConfig.service';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
|
||||
@ApiTags('前台-配置')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/config')
|
||||
export class ApiConfigController {
|
||||
constructor(private readonly service: SysConfigService) {}
|
||||
|
||||
@Get(':key')
|
||||
@ApiOperation({ summary: '按 key 获取配置(前台)' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getByKey(@Req() req: any, @Param('key') key: string) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
return this.service.getByKey(key, siteId);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '按 keys 批量获取配置(前台)' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getByKeys(@Req() req: any, @Query('keys') keys: string) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const list = (keys || '')
|
||||
.split(',')
|
||||
.map((k) => k.trim())
|
||||
.filter(Boolean);
|
||||
const out: Record<string, any> = {};
|
||||
for (const k of list) out[k] = await this.service.getByKey(k, siteId);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { SysDictService } from '../../services/core/sysDict.service';
|
||||
|
||||
@ApiTags('前台-字典')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/dict')
|
||||
export class ApiDictController {
|
||||
constructor(private readonly service: SysDictService) {}
|
||||
|
||||
@Get(':type/items')
|
||||
@ApiOperation({ summary: '获取某类型字典项(前台)' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async items(@Req() req: any, @Param('type') type: string) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
return this.service.listItems(siteId, type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
|
||||
@ApiTags('前台-系统首页')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/sys')
|
||||
export class SysIndexController {
|
||||
/**
|
||||
* 首页
|
||||
*/
|
||||
@Get('index')
|
||||
@ApiOperation({ summary: '系统首页' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async index(@Req() req: any) {
|
||||
return {
|
||||
code: 0,
|
||||
data: {
|
||||
message: 'NestJS API 服务运行正常',
|
||||
version: '1.0.0',
|
||||
timestamp: new Date().toISOString()
|
||||
},
|
||||
msg: 'success'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
*/
|
||||
@Get('health')
|
||||
@ApiOperation({ summary: '健康检查' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async health(@Req() req: any) {
|
||||
return {
|
||||
code: 0,
|
||||
data: {
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime()
|
||||
},
|
||||
msg: 'success'
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysAgreement } from '../../entity/sysAgreement.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SysAgreementService {
|
||||
constructor(
|
||||
@InjectRepository(SysAgreement)
|
||||
private readonly repo: Repository<SysAgreement>,
|
||||
) {}
|
||||
|
||||
async getList() {
|
||||
return this.repo.find({
|
||||
order: { create_time: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async getAgreement(key: string) {
|
||||
return this.repo.findOne({ where: { agreement_key: key } });
|
||||
}
|
||||
|
||||
async setAgreement(key: string, title: string, content: string) {
|
||||
const existing = await this.repo.findOne({ where: { agreement_key: key } });
|
||||
|
||||
if (existing) {
|
||||
await this.repo.update(existing.id, {
|
||||
title,
|
||||
content,
|
||||
update_time: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
} else {
|
||||
const agreement = this.repo.create({
|
||||
agreement_key: key,
|
||||
title,
|
||||
content,
|
||||
create_time: Math.floor(Date.now() / 1000),
|
||||
update_time: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
await this.repo.save(agreement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysArea } from '../../entity/sysArea.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SysAreaService {
|
||||
constructor(
|
||||
@InjectRepository(SysArea)
|
||||
private readonly areaRepo: Repository<SysArea>,
|
||||
) {}
|
||||
|
||||
async list(level?: number) {
|
||||
const where: any = {};
|
||||
if (typeof level !== 'undefined') where.level = level;
|
||||
return this.areaRepo.find({
|
||||
where,
|
||||
order: { sort: 'ASC', id: 'ASC' } as any,
|
||||
});
|
||||
}
|
||||
|
||||
async children(pid: number) {
|
||||
return this.areaRepo.find({
|
||||
where: { pid } as any,
|
||||
order: { sort: 'ASC', id: 'ASC' } as any,
|
||||
});
|
||||
}
|
||||
|
||||
async tree(level: number) {
|
||||
const all = await this.list();
|
||||
const levelAreas = all.filter((a) => a.level === level);
|
||||
|
||||
const buildTree = (parentId: number): any[] => {
|
||||
const children = all.filter((a) => a.pid === parentId);
|
||||
return children.map((child) => ({
|
||||
...child,
|
||||
children: buildTree(child.id),
|
||||
}));
|
||||
};
|
||||
|
||||
return levelAreas.map((area) => ({
|
||||
...area,
|
||||
children: buildTree(area.id),
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, FindOptionsOrder } from 'typeorm';
|
||||
import { SysConfig } from '../../entity/sysConfig.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SysConfigService {
|
||||
constructor(
|
||||
@InjectRepository(SysConfig)
|
||||
private readonly configRepo: Repository<SysConfig>,
|
||||
) {}
|
||||
|
||||
async list(siteId: number): Promise<SysConfig[]> {
|
||||
const order: FindOptionsOrder<SysConfig> = { id: 'ASC' };
|
||||
return this.configRepo.find({ where: { site_id: siteId }, order });
|
||||
}
|
||||
|
||||
async getValue(siteId: number, configKey: string): Promise<string | null> {
|
||||
const row = await this.configRepo.findOne({
|
||||
where: { site_id: siteId, config_key: configKey },
|
||||
});
|
||||
return row?.value ?? null;
|
||||
}
|
||||
|
||||
async upsertValue(
|
||||
siteId: number,
|
||||
configKey: string,
|
||||
value: string,
|
||||
): Promise<void> {
|
||||
let row = await this.configRepo.findOne({
|
||||
where: { site_id: siteId, config_key: configKey },
|
||||
});
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
if (!row) {
|
||||
row = this.configRepo.create({
|
||||
site_id: siteId,
|
||||
config_key: configKey,
|
||||
value,
|
||||
status: 1,
|
||||
create_time: now,
|
||||
update_time: now,
|
||||
addon: '',
|
||||
});
|
||||
} else {
|
||||
row.value = value;
|
||||
row.update_time = now;
|
||||
}
|
||||
await this.configRepo.save(row);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysDict } from '../../entity/sysDict.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SysDictService {
|
||||
constructor(
|
||||
@InjectRepository(SysDict)
|
||||
private readonly dictRepo: Repository<SysDict>,
|
||||
) {}
|
||||
|
||||
async list() {
|
||||
return this.dictRepo.find({ order: { id: 'ASC' } as any });
|
||||
}
|
||||
|
||||
async getByKey(key: string) {
|
||||
return this.dictRepo.findOne({ where: { key } as any });
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysExport } from '../../entity/sysExport.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SysExportService {
|
||||
constructor(
|
||||
@InjectRepository(SysExport)
|
||||
private readonly repo: Repository<SysExport>,
|
||||
) {}
|
||||
|
||||
async getPage(siteId: number, query: any) {
|
||||
const { export_key, export_status, create_time } = query;
|
||||
const qb = this.repo.createQueryBuilder('export');
|
||||
|
||||
if (export_key) {
|
||||
qb.andWhere('export.export_key LIKE :export_key', { export_key: `%${export_key}%` });
|
||||
}
|
||||
if (export_status) {
|
||||
qb.andWhere('export.export_status = :export_status', { export_status });
|
||||
}
|
||||
if (create_time && create_time.length === 2) {
|
||||
qb.andWhere('export.create_time BETWEEN :start AND :end', {
|
||||
start: create_time[0],
|
||||
end: create_time[1],
|
||||
});
|
||||
}
|
||||
|
||||
qb.andWhere('export.site_id = :siteId', { siteId });
|
||||
qb.orderBy('export.create_time', 'DESC');
|
||||
|
||||
const [list, total] = await qb.getManyAndCount();
|
||||
return { list, total };
|
||||
}
|
||||
|
||||
async getExportStatus() {
|
||||
return [
|
||||
{ label: '待导出', value: 0 },
|
||||
{ label: '导出中', value: 1 },
|
||||
{ label: '导出成功', value: 2 },
|
||||
{ label: '导出失败', value: 3 },
|
||||
];
|
||||
}
|
||||
|
||||
async getExportDataType() {
|
||||
return {
|
||||
'member': '会员数据',
|
||||
'order': '订单数据',
|
||||
'goods': '商品数据',
|
||||
'pay': '支付数据',
|
||||
};
|
||||
}
|
||||
|
||||
async checkExportData(siteId: number, type: string, where: any) {
|
||||
// 检查是否有可导出的数据
|
||||
// 实际实现中应该根据type查询对应表的数据量
|
||||
return true;
|
||||
}
|
||||
|
||||
async exportData(siteId: number, type: string, where: any) {
|
||||
// 创建导出任务
|
||||
const exportRecord = this.repo.create({
|
||||
site_id: siteId,
|
||||
export_key: type,
|
||||
export_status: 0,
|
||||
create_time: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
await this.repo.save(exportRecord);
|
||||
|
||||
// 实际实现中应该加入队列异步处理
|
||||
return exportRecord;
|
||||
}
|
||||
|
||||
async del(id: number) {
|
||||
await this.repo.delete(id);
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, FindOptionsOrder } from 'typeorm';
|
||||
import { SysMenu } from '../../entity/sysMenu.entity';
|
||||
|
||||
export type SysMenuTreeNode = SysMenu & { children: SysMenuTreeNode[] };
|
||||
|
||||
@Injectable()
|
||||
export class SysMenuService {
|
||||
constructor(
|
||||
@InjectRepository(SysMenu)
|
||||
private readonly menuRepo: Repository<SysMenu>,
|
||||
) {}
|
||||
|
||||
async list(appType = 'admin'): Promise<SysMenu[]> {
|
||||
const order: FindOptionsOrder<SysMenu> = { sort: 'ASC', id: 'ASC' };
|
||||
return this.menuRepo.find({ where: { app_type: appType }, order });
|
||||
}
|
||||
|
||||
async tree(appType = 'admin'): Promise<SysMenuTreeNode[]> {
|
||||
const list = await this.list(appType);
|
||||
const parentKeyToChildren = new Map<string, SysMenu[]>();
|
||||
for (const item of list) {
|
||||
const parentKey = item.parent_key || '';
|
||||
const current = parentKeyToChildren.get(parentKey) ?? [];
|
||||
current.push(item);
|
||||
parentKeyToChildren.set(parentKey, current);
|
||||
}
|
||||
const build = (parentKey: string): SysMenuTreeNode[] => {
|
||||
const children = parentKeyToChildren.get(parentKey) ?? [];
|
||||
return children.map((node) => ({
|
||||
...node,
|
||||
children: build(node.menu_key),
|
||||
}));
|
||||
};
|
||||
return build('');
|
||||
}
|
||||
|
||||
async findOne(appType: string, menuKey: string): Promise<SysMenu | null> {
|
||||
return this.menuRepo.findOne({
|
||||
where: { app_type: appType, menu_key: menuKey },
|
||||
});
|
||||
}
|
||||
|
||||
async createByKey(payload: Partial<SysMenu>) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const row = this.menuRepo.create({
|
||||
app_type: payload.app_type || 'admin',
|
||||
menu_key: payload.menu_key || '',
|
||||
parent_key: payload.parent_key || '',
|
||||
menu_name: payload.menu_name || '',
|
||||
menu_short_name: payload.menu_short_name || '',
|
||||
menu_type: payload.menu_type ?? 1,
|
||||
icon: payload.icon || '',
|
||||
api_url: payload.api_url || '',
|
||||
router_path: payload.router_path || '',
|
||||
view_path: payload.view_path || '',
|
||||
methods: payload.methods || '',
|
||||
sort: payload.sort ?? 1,
|
||||
status: typeof payload.status === 'number' ? payload.status : 1,
|
||||
is_show: typeof payload.is_show === 'number' ? payload.is_show : 1,
|
||||
addon: payload.addon || '',
|
||||
source: payload.source || 'system',
|
||||
menu_attr: payload.menu_attr || '',
|
||||
parent_select_key: payload.parent_select_key || '',
|
||||
create_time: now,
|
||||
delete_time: 0,
|
||||
});
|
||||
return this.menuRepo.save(row);
|
||||
}
|
||||
|
||||
async updateByKey(
|
||||
appType: string,
|
||||
menuKey: string,
|
||||
payload: Partial<SysMenu>,
|
||||
) {
|
||||
const exist = await this.findOne(appType, menuKey);
|
||||
if (!exist) return null;
|
||||
Object.assign(exist, payload);
|
||||
return this.menuRepo.save(exist);
|
||||
}
|
||||
|
||||
async deleteByKey(appType: string, menuKey: string) {
|
||||
const exist = await this.findOne(appType, menuKey);
|
||||
if (!exist) return 0;
|
||||
await this.menuRepo.delete({ id: exist.id });
|
||||
return 1;
|
||||
}
|
||||
|
||||
async listDir(appType: string): Promise<SysMenu[]> {
|
||||
return this.menuRepo.find({
|
||||
where: { app_type: appType, menu_type: 0 },
|
||||
order: { sort: 'ASC', id: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
async refreshMenu() {
|
||||
// 刷新菜单 - 调用安装系统服务安装菜单
|
||||
// 实际实现中应该调用 InstallSystemService.install()
|
||||
console.log('刷新菜单 - 重新安装系统菜单');
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, FindOptionsOrder } from 'typeorm';
|
||||
import { SysRole } from '../../entity/sysRole.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SysRoleService {
|
||||
constructor(
|
||||
@InjectRepository(SysRole)
|
||||
private readonly roleRepo: Repository<SysRole>,
|
||||
) {}
|
||||
|
||||
async list(siteId: number): Promise<SysRole[]> {
|
||||
const order: FindOptionsOrder<SysRole> = { role_id: 'ASC' };
|
||||
return this.roleRepo.find({ where: { site_id: siteId }, order });
|
||||
}
|
||||
|
||||
async detail(roleId: number): Promise<SysRole> {
|
||||
const role = await this.roleRepo.findOne({ where: { role_id: roleId } });
|
||||
if (!role) throw new NotFoundException('角色不存在');
|
||||
return role;
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysSchedule } from '../../entity/sysSchedule.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SysScheduleService {
|
||||
constructor(
|
||||
@InjectRepository(SysSchedule)
|
||||
private readonly repo: Repository<SysSchedule>,
|
||||
) {}
|
||||
|
||||
async getPage(siteId: number, query: any) {
|
||||
const { key, status } = query;
|
||||
const qb = this.repo.createQueryBuilder('schedule');
|
||||
|
||||
if (key) {
|
||||
qb.andWhere('schedule.key LIKE :key', { key: `%${key}%` });
|
||||
}
|
||||
if (status && status !== 'all') {
|
||||
qb.andWhere('schedule.status = :status', { status });
|
||||
}
|
||||
|
||||
qb.andWhere('schedule.site_id = :siteId', { siteId });
|
||||
qb.orderBy('schedule.create_time', 'DESC');
|
||||
|
||||
const [list, total] = await qb.getManyAndCount();
|
||||
return { list, total };
|
||||
}
|
||||
|
||||
async getTemplateList() {
|
||||
return [
|
||||
{ key: 'test', name: '测试任务', description: '用于测试的定时任务' },
|
||||
{ key: 'cleanup', name: '清理任务', description: '清理过期数据' },
|
||||
];
|
||||
}
|
||||
|
||||
async getType() {
|
||||
return [
|
||||
{ label: '定时执行', value: 1 },
|
||||
{ label: '间隔执行', value: 2 },
|
||||
];
|
||||
}
|
||||
|
||||
async getInfo(id: number) {
|
||||
return this.repo.findOne({ where: { id } });
|
||||
}
|
||||
|
||||
async add(siteId: number, data: any) {
|
||||
const schedule = this.repo.create({
|
||||
site_id: siteId,
|
||||
key: data.key,
|
||||
time: JSON.stringify(data.time || []),
|
||||
status: data.status || 0,
|
||||
create_time: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
return this.repo.save(schedule);
|
||||
}
|
||||
|
||||
async edit(id: number, data: any) {
|
||||
const updateData: any = {};
|
||||
if (data.time) updateData.time = JSON.stringify(data.time);
|
||||
if (data.status !== undefined) updateData.status = data.status;
|
||||
|
||||
await this.repo.update(id, updateData);
|
||||
}
|
||||
|
||||
async modifyStatus(id: number, status: number) {
|
||||
await this.repo.update(id, { status });
|
||||
}
|
||||
|
||||
async del(id: number) {
|
||||
await this.repo.delete(id);
|
||||
}
|
||||
|
||||
async getDateType() {
|
||||
return [
|
||||
{ label: '秒', value: 'second' },
|
||||
{ label: '分钟', value: 'minute' },
|
||||
{ label: '小时', value: 'hour' },
|
||||
{ label: '天', value: 'day' },
|
||||
];
|
||||
}
|
||||
|
||||
async doSchedule(id: number) {
|
||||
// 执行一次任务
|
||||
const schedule = await this.repo.findOne({ where: { id } });
|
||||
if (schedule) {
|
||||
// 实际实现中应该调用对应的任务处理器
|
||||
console.log(`执行任务: ${schedule.key}`);
|
||||
}
|
||||
}
|
||||
|
||||
async resetSchedule() {
|
||||
// 重置所有定时任务
|
||||
await this.repo.update({}, { status: 0 });
|
||||
}
|
||||
|
||||
async getLogList(query: any) {
|
||||
// 获取任务执行记录
|
||||
return { list: [], total: 0 };
|
||||
}
|
||||
|
||||
async logDelete(data: any) {
|
||||
// 删除执行记录
|
||||
}
|
||||
|
||||
async logClear() {
|
||||
// 清空执行记录
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysUser } from '../../entity/sysUser.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SysUserService {
|
||||
constructor(
|
||||
@InjectRepository(SysUser)
|
||||
private readonly sysUserRepository: Repository<SysUser>,
|
||||
) {}
|
||||
|
||||
async list(page = 1, limit = 10) {
|
||||
const [list, total] = await this.sysUserRepository.findAndCount({
|
||||
skip: (page - 1) * limit,
|
||||
take: limit,
|
||||
order: { uid: 'DESC' },
|
||||
});
|
||||
return { list, total, page, limit };
|
||||
}
|
||||
|
||||
async detail(uid: number) {
|
||||
const item = await this.sysUserRepository.findOne({ where: { uid } });
|
||||
if (!item) throw new NotFoundException('用户不存在');
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysUserLog } from '../../entity/sysUserLog.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SysUserLogService {
|
||||
constructor(
|
||||
@InjectRepository(SysUserLog)
|
||||
private readonly logRepo: Repository<SysUserLog>,
|
||||
) {}
|
||||
|
||||
async list(siteId?: number, uid?: number, page = 1, limit = 20) {
|
||||
const qb = this.logRepo.createQueryBuilder('l').orderBy('l.id', 'DESC');
|
||||
if (siteId) qb.andWhere('l.site_id = :siteId', { siteId });
|
||||
if (uid) qb.andWhere('l.uid = :uid', { uid });
|
||||
qb.skip((page - 1) * limit).take(limit);
|
||||
const [list, total] = await qb.getManyAndCount();
|
||||
return { list, total, page, limit };
|
||||
}
|
||||
}
|
||||
128
wwjcloud/src/common/sys/services/area.service.ts
Normal file
128
wwjcloud/src/common/sys/services/area.service.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysArea } from '../entity/sysArea.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AreaService {
|
||||
constructor(
|
||||
@InjectRepository(SysArea)
|
||||
private readonly areaRepo: Repository<SysArea>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取地区信息
|
||||
* @param pid 上级pid
|
||||
*/
|
||||
async getListByPid(pid: number = 0) {
|
||||
const areas = await this.areaRepo.find({
|
||||
where: { pid },
|
||||
select: ['id', 'name']
|
||||
});
|
||||
return areas;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询地区树列表
|
||||
* @param level 层级1,2,3
|
||||
*/
|
||||
async getAreaTree(level: number = 3) {
|
||||
const areas = await this.areaRepo.find({
|
||||
where: { level: level },
|
||||
select: ['id', 'pid', 'name']
|
||||
});
|
||||
|
||||
// 构建树形结构
|
||||
return this.buildTree(areas);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过编码查询地址信息
|
||||
* @param id 地区ID
|
||||
*/
|
||||
async getAreaByAreaCode(id: number) {
|
||||
const levelMap: { [key: number]: string } = { 1: 'province', 2: 'city', 3: 'district' };
|
||||
const tree: any = {};
|
||||
|
||||
let area = await this.areaRepo.findOne({
|
||||
where: { id },
|
||||
select: ['id', 'level', 'pid', 'name']
|
||||
});
|
||||
|
||||
if (area) {
|
||||
tree[levelMap[area.level]] = area;
|
||||
|
||||
while (area && area.level > 1) {
|
||||
area = await this.areaRepo.findOne({
|
||||
where: { id: area.pid },
|
||||
select: ['id', 'level', 'pid', 'name']
|
||||
});
|
||||
if (area) {
|
||||
tree[levelMap[area.level]] = area;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过经纬度查询地址
|
||||
* @param latlng 经纬度
|
||||
*/
|
||||
async getAddressByLatlng(latlng: string) {
|
||||
// 这里需要调用地图API,暂时返回空对象避免硬编码
|
||||
// 实际实现需要根据地图服务商的API进行调用
|
||||
return {
|
||||
province_id: 0,
|
||||
province: '',
|
||||
city_id: 0,
|
||||
city: '',
|
||||
district_id: 0,
|
||||
district: '',
|
||||
community: '',
|
||||
full_address: '',
|
||||
formatted_addresses: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有地区列表
|
||||
*/
|
||||
async list() {
|
||||
return await this.areaRepo.find({
|
||||
select: ['id', 'pid', 'name', 'level']
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地区树
|
||||
*/
|
||||
async tree(level: number = 3) {
|
||||
const areas = await this.areaRepo.find({
|
||||
where: { level: level },
|
||||
select: ['id', 'pid', 'name']
|
||||
});
|
||||
|
||||
return this.buildTree(areas);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建树形结构
|
||||
*/
|
||||
private buildTree(areas: any[], parentId: number = 0): any[] {
|
||||
const tree: any[] = [];
|
||||
|
||||
for (const area of areas) {
|
||||
if (area.pid === parentId) {
|
||||
const children = this.buildTree(areas, area.id);
|
||||
if (children.length > 0) {
|
||||
area.children = children;
|
||||
}
|
||||
tree.push(area);
|
||||
}
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
}
|
||||
116
wwjcloud/src/common/sys/services/config.service.ts
Normal file
116
wwjcloud/src/common/sys/services/config.service.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysConfig } from '../entity/sysConfig.entity';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigService {
|
||||
constructor(
|
||||
@InjectRepository(SysConfig)
|
||||
private readonly configRepo: Repository<SysConfig>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取版权信息(网站整体,不按照站点设置)
|
||||
*/
|
||||
async getCopyright(siteId: number) {
|
||||
const info = await this.configRepo.findOne({
|
||||
where: { site_id: siteId, config_key: 'COPYRIGHT' }
|
||||
});
|
||||
|
||||
if (!info) {
|
||||
return {
|
||||
icp: '',
|
||||
gov_record: '',
|
||||
gov_url: '',
|
||||
market_supervision_url: '',
|
||||
logo: '',
|
||||
company_name: '',
|
||||
copyright_link: '',
|
||||
copyright_desc: ''
|
||||
};
|
||||
}
|
||||
|
||||
return JSON.parse(info.value || '{}');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前端域名
|
||||
*/
|
||||
async getSceneDomain(siteId: number) {
|
||||
const wapDomain = process.env.WAP_DOMAIN || 'localhost';
|
||||
const webDomain = process.env.WEB_DOMAIN || 'localhost';
|
||||
const serviceDomain = 'localhost';
|
||||
|
||||
return {
|
||||
wap_domain: wapDomain,
|
||||
wap_url: `${wapDomain}/wap/${siteId}`,
|
||||
web_url: `${webDomain}/web/${siteId}`,
|
||||
service_domain: serviceDomain
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取手机端首页列表
|
||||
*/
|
||||
async getWapIndexList(data: any = {}) {
|
||||
// 这里需要根据实际业务逻辑实现
|
||||
// 暂时返回空数组,避免硬编码
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地图配置
|
||||
*/
|
||||
async getMap(siteId: number) {
|
||||
const mapConfig = await this.configRepo.findOne({
|
||||
where: { site_id: siteId, config_key: 'MAP' }
|
||||
});
|
||||
|
||||
if (!mapConfig) {
|
||||
return {
|
||||
key: '',
|
||||
type: 'amap'
|
||||
};
|
||||
}
|
||||
|
||||
return JSON.parse(mapConfig.value || '{}');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置值
|
||||
*/
|
||||
async getValue(siteId: number, key: string) {
|
||||
const config = await this.configRepo.findOne({
|
||||
where: { site_id: siteId, config_key: key }
|
||||
});
|
||||
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(config.value || '{}');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新或插入配置值
|
||||
*/
|
||||
async upsertValue(siteId: number, key: string, value: any) {
|
||||
const existing = await this.configRepo.findOne({
|
||||
where: { site_id: siteId, config_key: key }
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
existing.value = JSON.stringify(value);
|
||||
return await this.configRepo.save(existing);
|
||||
} else {
|
||||
const newConfig = this.configRepo.create({
|
||||
site_id: siteId,
|
||||
config_key: key,
|
||||
value: JSON.stringify(value),
|
||||
status: 1
|
||||
});
|
||||
return await this.configRepo.save(newConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, FindOptionsOrder } from 'typeorm';
|
||||
import { SysArea } from '../../entity/sysArea.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SysAreaService {
|
||||
constructor(
|
||||
@InjectRepository(SysArea)
|
||||
private readonly repo: Repository<SysArea>,
|
||||
) {}
|
||||
|
||||
async list(level?: number): Promise<SysArea[]> {
|
||||
const where: Partial<SysArea> = {};
|
||||
if (typeof level !== 'undefined') where.level = level;
|
||||
const order: FindOptionsOrder<SysArea> = { sort: 'ASC', id: 'ASC' };
|
||||
return this.repo.find({ where, order });
|
||||
}
|
||||
|
||||
async tree(level?: number): Promise<(SysArea & { children: SysArea[] })[]> {
|
||||
const all = await this.list(level);
|
||||
const idToNode = new Map<number, SysArea & { children: SysArea[] }>();
|
||||
for (const a of all) idToNode.set(a.id, { ...a, children: [] });
|
||||
const roots: (SysArea & { children: SysArea[] })[] = [];
|
||||
for (const a of all) {
|
||||
const node = idToNode.get(a.id)!;
|
||||
const parentId = a.pid || 0;
|
||||
if (parentId !== 0 && idToNode.has(parentId)) idToNode.get(parentId)!.children.push(node);
|
||||
else roots.push(node);
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysAudit } from '../../entity/sysAudit.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SysAuditService {
|
||||
constructor(
|
||||
@InjectRepository(SysAudit)
|
||||
private readonly repo: Repository<SysAudit>,
|
||||
) {}
|
||||
|
||||
async write(options: {
|
||||
siteId: number;
|
||||
module: string;
|
||||
action: string;
|
||||
operatorId?: number;
|
||||
operatorName?: string;
|
||||
before?: any;
|
||||
after?: any;
|
||||
traceId?: string;
|
||||
ip?: string;
|
||||
extra?: Record<string, any>;
|
||||
}): Promise<void> {
|
||||
const row = this.repo.create({
|
||||
site_id: options.siteId,
|
||||
module: options.module || 'sys',
|
||||
action: options.action || '',
|
||||
operator_id: options.operatorId || 0,
|
||||
operator_name: options.operatorName || '',
|
||||
before_value:
|
||||
options.before == null ? null : JSON.stringify(options.before),
|
||||
after_value: options.after == null ? null : JSON.stringify(options.after),
|
||||
trace_id: options.traceId || '',
|
||||
ip: options.ip || '',
|
||||
extra: options.extra ? JSON.stringify(options.extra) : null,
|
||||
create_time: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
await this.repo.save(row);
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysConfig } from '../../entity/sysConfig.entity';
|
||||
import { SysAuditService } from './sysAudit.service';
|
||||
|
||||
@Injectable()
|
||||
export class SysConfigService {
|
||||
constructor(
|
||||
@InjectRepository(SysConfig)
|
||||
private readonly repo: Repository<SysConfig>,
|
||||
private readonly audit: SysAuditService,
|
||||
) {}
|
||||
|
||||
async getList(
|
||||
siteId: number = 0,
|
||||
): Promise<Array<{ key: string; value: any }>> {
|
||||
const rows = await this.repo.find({ where: { site_id: siteId } as any });
|
||||
return rows.map((r: any) => ({
|
||||
key: r.config_key,
|
||||
value: this.parseValue(r.value),
|
||||
}));
|
||||
}
|
||||
|
||||
async getByKey(key: string, siteId: number = 0): Promise<any | null> {
|
||||
const row: any = await this.repo.findOne({
|
||||
where: { site_id: siteId, config_key: key } as any,
|
||||
});
|
||||
return row ? this.parseValue(row.value) : null;
|
||||
}
|
||||
|
||||
async setByKey(
|
||||
key: string,
|
||||
value: any,
|
||||
siteId: number = 0,
|
||||
auditContext?: {
|
||||
operatorId?: number;
|
||||
operatorName?: string;
|
||||
ip?: string;
|
||||
traceId?: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
const payload = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
const exist: any = await this.repo.findOne({
|
||||
where: { site_id: siteId, config_key: key } as any,
|
||||
});
|
||||
if (exist) {
|
||||
const before = { value: this.parseValue(exist.value) };
|
||||
exist.value = payload;
|
||||
await this.repo.save(exist);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_config',
|
||||
action: 'update',
|
||||
before,
|
||||
after: { value: this.parseValue(payload) },
|
||||
operatorId: auditContext?.operatorId,
|
||||
operatorName: auditContext?.operatorName,
|
||||
ip: auditContext?.ip,
|
||||
traceId: auditContext?.traceId,
|
||||
});
|
||||
} else {
|
||||
const row: any = this.repo.create({
|
||||
site_id: siteId,
|
||||
config_key: key,
|
||||
value: payload,
|
||||
});
|
||||
await this.repo.save(row);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_config',
|
||||
action: 'create',
|
||||
before: null,
|
||||
after: { value: this.parseValue(payload) },
|
||||
operatorId: auditContext?.operatorId,
|
||||
operatorName: auditContext?.operatorName,
|
||||
ip: auditContext?.ip,
|
||||
traceId: auditContext?.traceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async deleteByKey(key: string, siteId: number = 0): Promise<void> {
|
||||
const exist: any = await this.repo.findOne({
|
||||
where: { site_id: siteId, config_key: key } as any,
|
||||
});
|
||||
await this.repo.delete({ site_id: siteId, config_key: key } as any);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_config',
|
||||
action: 'delete',
|
||||
before: exist ? { value: this.parseValue(exist.value) } : null,
|
||||
after: null,
|
||||
});
|
||||
}
|
||||
|
||||
async getStats(siteId: number = 0): Promise<{ total: number }> {
|
||||
const total = await this.repo.count({ where: { site_id: siteId } as any });
|
||||
return { total };
|
||||
}
|
||||
|
||||
async getSystemSnapshot(siteId: number = 0): Promise<Record<string, any>> {
|
||||
const list = await this.getList(siteId);
|
||||
const out: Record<string, any> = {};
|
||||
for (const item of list) out[item.key] = item.value;
|
||||
return out;
|
||||
}
|
||||
|
||||
private parseValue(val: any): any {
|
||||
if (val == null) return null;
|
||||
if (typeof val !== 'string') return val;
|
||||
try {
|
||||
return JSON.parse(val);
|
||||
} catch {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysDictType } from '../../entity/sysDictType.entity';
|
||||
import { SysDictItem } from '../../entity/sysDictItem.entity';
|
||||
import { SysAuditService } from './sysAudit.service';
|
||||
import { CacheService } from '../../../../core/cache/cacheService';
|
||||
|
||||
@Injectable()
|
||||
export class SysDictService {
|
||||
constructor(
|
||||
@InjectRepository(SysDictType)
|
||||
private readonly typeRepo: Repository<SysDictType>,
|
||||
@InjectRepository(SysDictItem)
|
||||
private readonly itemRepo: Repository<SysDictItem>,
|
||||
private readonly audit: SysAuditService,
|
||||
private readonly cache: CacheService,
|
||||
) {}
|
||||
|
||||
async listTypes(siteId: number): Promise<SysDictType[]> {
|
||||
const key = `sys:dict:types:${siteId}`;
|
||||
return this.cache.wrap(
|
||||
key,
|
||||
() =>
|
||||
this.typeRepo.find({
|
||||
where: { site_id: siteId } as any,
|
||||
order: { id: 'ASC' } as any,
|
||||
}),
|
||||
30,
|
||||
);
|
||||
}
|
||||
|
||||
async listItems(siteId: number, type: string): Promise<SysDictItem[]> {
|
||||
const key = `sys:dict:items:${siteId}:${type}`;
|
||||
return this.cache.wrap(
|
||||
key,
|
||||
() =>
|
||||
this.itemRepo.find({
|
||||
where: { site_id: siteId, type } as any,
|
||||
order: { sort: 'ASC', id: 'ASC' } as any,
|
||||
}),
|
||||
30,
|
||||
);
|
||||
}
|
||||
|
||||
async createType(
|
||||
siteId: number,
|
||||
dto: { type: string; name: string },
|
||||
actor?: { id?: number; name?: string; ip?: string; trace?: string },
|
||||
) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const row = this.typeRepo.create({
|
||||
site_id: siteId,
|
||||
type: dto.type,
|
||||
name: dto.name,
|
||||
status: 1,
|
||||
create_time: now,
|
||||
update_time: now,
|
||||
});
|
||||
const saved = await this.typeRepo.save(row);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_dict',
|
||||
action: 'type.create',
|
||||
before: null,
|
||||
after: saved,
|
||||
operatorId: actor?.id,
|
||||
operatorName: actor?.name,
|
||||
ip: actor?.ip,
|
||||
traceId: actor?.trace,
|
||||
});
|
||||
await this.cache.del(`sys:dict:types:${siteId}`);
|
||||
return saved;
|
||||
}
|
||||
|
||||
async updateType(
|
||||
siteId: number,
|
||||
id: number,
|
||||
dto: Partial<SysDictType>,
|
||||
actor?: { id?: number; name?: string; ip?: string; trace?: string },
|
||||
) {
|
||||
const exist = await this.typeRepo.findOne({
|
||||
where: { id, site_id: siteId } as any,
|
||||
});
|
||||
if (!exist) return null;
|
||||
const before = { ...exist };
|
||||
Object.assign(exist, dto, { update_time: Math.floor(Date.now() / 1000) });
|
||||
const saved = await this.typeRepo.save(exist);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_dict',
|
||||
action: 'type.update',
|
||||
before,
|
||||
after: saved,
|
||||
operatorId: actor?.id,
|
||||
operatorName: actor?.name,
|
||||
ip: actor?.ip,
|
||||
traceId: actor?.trace,
|
||||
});
|
||||
await this.cache.del(`sys:dict:types:${siteId}`);
|
||||
return saved;
|
||||
}
|
||||
|
||||
async removeType(
|
||||
siteId: number,
|
||||
id: number,
|
||||
actor?: { id?: number; name?: string; ip?: string; trace?: string },
|
||||
) {
|
||||
const exist = await this.typeRepo.findOne({
|
||||
where: { id, site_id: siteId } as any,
|
||||
});
|
||||
if (!exist) return 0;
|
||||
await this.typeRepo.delete({ id, site_id: siteId } as any);
|
||||
await this.cache.del(`sys:dict:types:${siteId}`);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_dict',
|
||||
action: 'type.delete',
|
||||
before: exist,
|
||||
after: null,
|
||||
operatorId: actor?.id,
|
||||
operatorName: actor?.name,
|
||||
ip: actor?.ip,
|
||||
traceId: actor?.trace,
|
||||
});
|
||||
// note: items retention policy is business-specific; no cascade delete here
|
||||
return 1;
|
||||
}
|
||||
|
||||
async createItem(
|
||||
siteId: number,
|
||||
dto: { type: string; label: string; value: string; sort?: number },
|
||||
actor?: { id?: number; name?: string; ip?: string; trace?: string },
|
||||
) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const row = this.itemRepo.create({
|
||||
site_id: siteId,
|
||||
type: dto.type,
|
||||
label: dto.label,
|
||||
value: dto.value,
|
||||
sort: dto.sort ?? 0,
|
||||
status: 1,
|
||||
create_time: now,
|
||||
update_time: now,
|
||||
});
|
||||
const saved = await this.itemRepo.save(row);
|
||||
await this.cache.del(`sys:dict:items:${siteId}:${dto.type}`);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_dict',
|
||||
action: 'item.create',
|
||||
before: null,
|
||||
after: saved,
|
||||
operatorId: actor?.id,
|
||||
operatorName: actor?.name,
|
||||
ip: actor?.ip,
|
||||
traceId: actor?.trace,
|
||||
});
|
||||
return saved;
|
||||
}
|
||||
|
||||
async updateItem(
|
||||
siteId: number,
|
||||
id: number,
|
||||
dto: Partial<SysDictItem>,
|
||||
actor?: { id?: number; name?: string; ip?: string; trace?: string },
|
||||
) {
|
||||
const exist = await this.itemRepo.findOne({
|
||||
where: { id, site_id: siteId } as any,
|
||||
});
|
||||
if (!exist) return null;
|
||||
const before = { ...exist };
|
||||
Object.assign(exist, dto, { update_time: Math.floor(Date.now() / 1000) });
|
||||
const saved = await this.itemRepo.save(exist);
|
||||
await this.cache.del(`sys:dict:items:${siteId}:${exist.type}`);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_dict',
|
||||
action: 'item.update',
|
||||
before,
|
||||
after: saved,
|
||||
operatorId: actor?.id,
|
||||
operatorName: actor?.name,
|
||||
ip: actor?.ip,
|
||||
traceId: actor?.trace,
|
||||
});
|
||||
return saved;
|
||||
}
|
||||
|
||||
async removeItem(
|
||||
siteId: number,
|
||||
id: number,
|
||||
actor?: { id?: number; name?: string; ip?: string; trace?: string },
|
||||
) {
|
||||
const exist = await this.itemRepo.findOne({
|
||||
where: { id, site_id: siteId } as any,
|
||||
});
|
||||
if (!exist) return 0;
|
||||
await this.itemRepo.delete({ id, site_id: siteId } as any);
|
||||
await this.cache.del(`sys:dict:items:${siteId}:${exist.type}`);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_dict',
|
||||
action: 'item.delete',
|
||||
before: exist,
|
||||
after: null,
|
||||
operatorId: actor?.id,
|
||||
operatorName: actor?.name,
|
||||
ip: actor?.ip,
|
||||
traceId: actor?.trace,
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, FindOptionsOrder } from 'typeorm';
|
||||
import { SysMenu } from '../../entity/sysMenu.entity';
|
||||
import { SysAuditService } from './sysAudit.service';
|
||||
import { CacheService } from '../../../../core/cache/cacheService';
|
||||
|
||||
@Injectable()
|
||||
export class SysMenuService {
|
||||
constructor(
|
||||
@InjectRepository(SysMenu)
|
||||
private readonly repo: Repository<SysMenu>,
|
||||
private readonly audit: SysAuditService,
|
||||
private readonly cache: CacheService,
|
||||
) {}
|
||||
|
||||
async list(appType = 'admin'): Promise<SysMenu[]> {
|
||||
const key = `sys:menu:list:${appType}`;
|
||||
const order: FindOptionsOrder<SysMenu> = { sort: 'ASC', id: 'ASC' };
|
||||
return this.cache.wrap(key, () => this.repo.find({ where: { app_type: appType }, order }), 30);
|
||||
}
|
||||
|
||||
async tree(appType = 'admin'): Promise<(SysMenu & { children: SysMenu[] })[]> {
|
||||
const all = await this.list(appType);
|
||||
const keyToNode = new Map<string, SysMenu & { children: SysMenu[] }>();
|
||||
for (const m of all) keyToNode.set(m.menu_key, { ...m, children: [] });
|
||||
const roots: (SysMenu & { children: SysMenu[] })[] = [];
|
||||
for (const m of all) {
|
||||
const node = keyToNode.get(m.menu_key)!;
|
||||
const parentKey = m.parent_key || '';
|
||||
if (parentKey && keyToNode.has(parentKey)) keyToNode.get(parentKey)!.children.push(node);
|
||||
else roots.push(node);
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
async create(
|
||||
appType: string,
|
||||
payload: Partial<SysMenu>,
|
||||
siteId: number,
|
||||
actor?: { id?: number; name?: string; ip?: string; trace?: string },
|
||||
) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const row = this.repo.create({
|
||||
app_type: appType || 'admin',
|
||||
menu_name: payload.menu_name ?? '',
|
||||
menu_short_name: payload.menu_short_name ?? '',
|
||||
menu_key: payload.menu_key ?? '',
|
||||
parent_key: payload.parent_key ?? '',
|
||||
menu_type: payload.menu_type ?? 1,
|
||||
icon: payload.icon ?? '',
|
||||
api_url: payload.api_url ?? '',
|
||||
router_path: payload.router_path ?? '',
|
||||
view_path: payload.view_path ?? '',
|
||||
methods: payload.methods ?? '',
|
||||
sort: payload.sort ?? 1,
|
||||
status: typeof payload.status === 'number' ? payload.status : 1,
|
||||
is_show: typeof payload.is_show === 'number' ? payload.is_show : 1,
|
||||
create_time: now,
|
||||
delete_time: 0,
|
||||
addon: payload.addon ?? '',
|
||||
source: payload.source ?? 'system',
|
||||
menu_attr: payload.menu_attr ?? '',
|
||||
parent_select_key: payload.parent_select_key ?? '',
|
||||
});
|
||||
const saved = await this.repo.save(row);
|
||||
await this.cache.del(`sys:menu:list:${appType || 'admin'}`);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_menu',
|
||||
action: 'create',
|
||||
before: null,
|
||||
after: saved,
|
||||
operatorId: actor?.id,
|
||||
operatorName: actor?.name,
|
||||
ip: actor?.ip,
|
||||
traceId: actor?.trace,
|
||||
});
|
||||
return saved;
|
||||
}
|
||||
|
||||
async update(
|
||||
id: number,
|
||||
payload: Partial<SysMenu>,
|
||||
siteId: number,
|
||||
actor?: { id?: number; name?: string; ip?: string; trace?: string },
|
||||
) {
|
||||
const exist = await this.repo.findOne({ where: { id } });
|
||||
if (!exist) return null;
|
||||
const before = { ...exist };
|
||||
Object.assign(exist, payload);
|
||||
const saved = await this.repo.save(exist);
|
||||
await this.cache.del(`sys:menu:list:${exist.app_type}`);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_menu',
|
||||
action: 'update',
|
||||
before,
|
||||
after: saved,
|
||||
operatorId: actor?.id,
|
||||
operatorName: actor?.name,
|
||||
ip: actor?.ip,
|
||||
traceId: actor?.trace,
|
||||
});
|
||||
return saved;
|
||||
}
|
||||
|
||||
async remove(id: number, siteId: number, actor?: { id?: number; name?: string; ip?: string; trace?: string }) {
|
||||
const exist = await this.repo.findOne({ where: { id } });
|
||||
if (!exist) return 0;
|
||||
await this.repo.delete({ id });
|
||||
await this.cache.del(`sys:menu:list:${exist.app_type}`);
|
||||
await this.audit.write({
|
||||
siteId,
|
||||
module: 'sys_menu',
|
||||
action: 'delete',
|
||||
before: exist,
|
||||
after: null,
|
||||
operatorId: actor?.id,
|
||||
operatorName: actor?.name,
|
||||
ip: actor?.ip,
|
||||
traceId: actor?.trace,
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { SysUser } from './entity/sysUser.entity';
|
||||
import { SysMenu } from './entity/sysMenu.entity';
|
||||
@@ -11,27 +11,17 @@ import { SysUserLog } from './entity/sysUserLog.entity';
|
||||
import { SysExport } from './entity/sysExport.entity';
|
||||
import { SysSchedule } from './entity/sysSchedule.entity';
|
||||
import { SysAgreement } from './entity/sysAgreement.entity';
|
||||
import { SysUserService } from './services/admin/sysUser.service';
|
||||
import { SysMenuService } from './services/admin/sysMenu.service';
|
||||
import { SysConfigService } from './services/admin/sysConfig.service';
|
||||
import { SysRoleService } from './services/admin/sysRole.service';
|
||||
import { SysAreaService } from './services/admin/sysArea.service';
|
||||
import { SysDictService } from './services/admin/sysDict.service';
|
||||
import { SysUserLogService } from './services/admin/sysUserLog.service';
|
||||
import { SysExportService } from './services/admin/sysExport.service';
|
||||
import { SysScheduleService } from './services/admin/sysSchedule.service';
|
||||
import { SysAgreementService } from './services/admin/sysAgreement.service';
|
||||
import { SysMenuController } from './controllers/adminapi/sysMenu.controller';
|
||||
// 扁平化服务 - 直接对应 PHP 项目
|
||||
import { ConfigService } from './services/config.service';
|
||||
import { AreaService } from './services/area.service';
|
||||
import { SysConfigController } from './controllers/adminapi/sysConfig.controller';
|
||||
import { SysAreaController } from './controllers/adminapi/areaController';
|
||||
import { SysUserLogController } from './controllers/adminapi/sysUserLog.controller';
|
||||
import { SysMenuRefreshController } from './controllers/adminapi/sysMenuRefresh.controller';
|
||||
import { SysExportController } from './controllers/adminapi/sysExport.controller';
|
||||
import { SysScheduleController } from './controllers/adminapi/sysSchedule.controller';
|
||||
import { SysAgreementController } from './controllers/adminapi/sysAgreement.controller';
|
||||
import { SysWebController } from './controllers/adminapi/sysWeb.controller';
|
||||
import { SysAreaController } from './controllers/adminapi/AreaController';
|
||||
import { AuditService } from '../../core/audit/auditService';
|
||||
import { SysMiscController } from './controllers/adminapi/sysMisc.controller';
|
||||
// 扁平化控制器 - 直接对应 PHP 项目
|
||||
import { ConfigController } from './controllers/api/config.controller';
|
||||
import { AreaController } from './controllers/api/areaController';
|
||||
import { SysIndexController } from './controllers/api/sysIndex.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -50,41 +40,28 @@ import { SysMiscController } from './controllers/adminapi/sysMisc.controller';
|
||||
]),
|
||||
],
|
||||
controllers: [
|
||||
SysMenuController,
|
||||
SysConfigController,
|
||||
SysAreaController,
|
||||
SysUserLogController,
|
||||
SysMenuRefreshController,
|
||||
SysExportController,
|
||||
SysScheduleController,
|
||||
SysAgreementController,
|
||||
SysWebController,
|
||||
SysMiscController,
|
||||
// 扁平化控制器 - 直接对应 PHP 项目
|
||||
ConfigController,
|
||||
AreaController,
|
||||
SysIndexController,
|
||||
],
|
||||
providers: [
|
||||
SysUserService,
|
||||
SysMenuService,
|
||||
SysConfigService,
|
||||
SysRoleService,
|
||||
SysAreaService,
|
||||
SysDictService,
|
||||
SysUserLogService,
|
||||
SysExportService,
|
||||
SysScheduleService,
|
||||
SysAgreementService,
|
||||
// 扁平化服务 - 直接对应 PHP 项目
|
||||
ConfigService,
|
||||
AreaService,
|
||||
|
||||
// 其他服务
|
||||
AuditService,
|
||||
],
|
||||
exports: [
|
||||
SysUserService,
|
||||
SysMenuService,
|
||||
SysConfigService,
|
||||
SysRoleService,
|
||||
SysAreaService,
|
||||
SysDictService,
|
||||
SysUserLogService,
|
||||
SysExportService,
|
||||
SysScheduleService,
|
||||
SysAgreementService,
|
||||
// 扁平化服务 - 直接对应 PHP 项目
|
||||
ConfigService,
|
||||
AreaService,
|
||||
|
||||
// 其他服务
|
||||
AuditService,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Controller, Post, Body, Req, UseGuards, UseInterceptors, UploadedFile } from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { ApiOperation, ApiResponse, ApiTags, ApiConsumes } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { UploadService } from '../../services/upload.service';
|
||||
|
||||
@ApiTags('前台-上传')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/upload')
|
||||
export class UploadController {
|
||||
constructor(private readonly uploadService: UploadService) {}
|
||||
|
||||
/**
|
||||
* 图片上传
|
||||
*/
|
||||
@Post('image')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiOperation({ summary: '图片上传' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async image(@UploadedFile() file: any, @Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.uploadService.image(file, siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频上传
|
||||
*/
|
||||
@Post('video')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiOperation({ summary: '视频上传' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async video(@UploadedFile() file: any, @Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.uploadService.video(file, siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程图片拉取
|
||||
*/
|
||||
@Post('fetch')
|
||||
@ApiOperation({ summary: '远程图片拉取' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async fetch(@Body('url') url: string, @Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.uploadService.fetch(url, siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64上传
|
||||
*/
|
||||
@Post('base64')
|
||||
@ApiOperation({ summary: 'Base64上传' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async base64(@Body('base64') base64: string, @Req() req: any) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.uploadService.base64(base64, siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
71
wwjcloud/src/common/upload/entity/upload.entity.ts
Normal file
71
wwjcloud/src/common/upload/entity/upload.entity.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('attachment')
|
||||
export class Attachment {
|
||||
@PrimaryGeneratedColumn({ name: 'id', type: 'int', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'site_id', type: 'int', nullable: false, default: () => '0' })
|
||||
siteId: number;
|
||||
|
||||
@Column({
|
||||
name: 'name',
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
name: string;
|
||||
|
||||
@Column({
|
||||
name: 'url',
|
||||
type: 'varchar',
|
||||
length: 500,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
url: string;
|
||||
|
||||
@Column({
|
||||
name: 'path',
|
||||
type: 'varchar',
|
||||
length: 500,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
path: string;
|
||||
|
||||
@Column({
|
||||
name: 'ext',
|
||||
type: 'varchar',
|
||||
length: 20,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
ext: string;
|
||||
|
||||
@Column({
|
||||
name: 'size',
|
||||
type: 'int',
|
||||
nullable: false,
|
||||
default: () => '0',
|
||||
})
|
||||
size: number;
|
||||
|
||||
@Column({
|
||||
name: 'type',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
type: string;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
}
|
||||
130
wwjcloud/src/common/upload/services/upload.service.ts
Normal file
130
wwjcloud/src/common/upload/services/upload.service.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Attachment } from '../entity/upload.entity';
|
||||
|
||||
@Injectable()
|
||||
export class UploadService {
|
||||
constructor(
|
||||
@InjectRepository(Attachment)
|
||||
private readonly attachmentRepo: Repository<Attachment>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 图片上传
|
||||
*/
|
||||
async image(file: any, siteId: number) {
|
||||
// 这里需要实现实际的文件上传逻辑
|
||||
// 暂时返回模拟数据,避免硬编码
|
||||
const fileName = file.originalname || 'image.jpg';
|
||||
const fileExt = fileName.split('.').pop() || 'jpg';
|
||||
const fileSize = file.size || 0;
|
||||
|
||||
const attachment = this.attachmentRepo.create({
|
||||
siteId,
|
||||
name: fileName,
|
||||
url: `/uploads/image/${siteId}/${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}/${String(new Date().getDate()).padStart(2, '0')}/${fileName}`,
|
||||
path: `file/image/${siteId}/${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}/${String(new Date().getDate()).padStart(2, '0')}/${fileName}`,
|
||||
ext: fileExt,
|
||||
size: fileSize,
|
||||
type: 'image'
|
||||
});
|
||||
|
||||
const result = await this.attachmentRepo.save(attachment);
|
||||
return {
|
||||
id: result.id,
|
||||
name: result.name,
|
||||
url: result.url,
|
||||
path: result.path,
|
||||
ext: result.ext,
|
||||
size: result.size
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频上传
|
||||
*/
|
||||
async video(file: any, siteId: number) {
|
||||
const fileName = file.originalname || 'video.mp4';
|
||||
const fileExt = fileName.split('.').pop() || 'mp4';
|
||||
const fileSize = file.size || 0;
|
||||
|
||||
const attachment = this.attachmentRepo.create({
|
||||
siteId,
|
||||
name: fileName,
|
||||
url: `/uploads/video/${siteId}/${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}/${String(new Date().getDate()).padStart(2, '0')}/${fileName}`,
|
||||
path: `file/video/${siteId}/${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}/${String(new Date().getDate()).padStart(2, '0')}/${fileName}`,
|
||||
ext: fileExt,
|
||||
size: fileSize,
|
||||
type: 'video'
|
||||
});
|
||||
|
||||
const result = await this.attachmentRepo.save(attachment);
|
||||
return {
|
||||
id: result.id,
|
||||
name: result.name,
|
||||
url: result.url,
|
||||
path: result.path,
|
||||
ext: result.ext,
|
||||
size: result.size
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程图片拉取
|
||||
*/
|
||||
async fetch(url: string, siteId: number) {
|
||||
// 这里需要实现远程图片拉取逻辑
|
||||
// 暂时返回模拟数据,避免硬编码
|
||||
const fileName = `fetch_${Date.now()}.jpg`;
|
||||
|
||||
const attachment = this.attachmentRepo.create({
|
||||
siteId,
|
||||
name: fileName,
|
||||
url: `/uploads/fetch/${siteId}/${fileName}`,
|
||||
path: `file/fetch/${siteId}/${fileName}`,
|
||||
ext: 'jpg',
|
||||
size: 0,
|
||||
type: 'image'
|
||||
});
|
||||
|
||||
const result = await this.attachmentRepo.save(attachment);
|
||||
return {
|
||||
id: result.id,
|
||||
name: result.name,
|
||||
url: result.url,
|
||||
path: result.path,
|
||||
ext: result.ext,
|
||||
size: result.size
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64上传
|
||||
*/
|
||||
async base64(base64: string, siteId: number) {
|
||||
// 这里需要实现Base64上传逻辑
|
||||
// 暂时返回模拟数据,避免硬编码
|
||||
const fileName = `base64_${Date.now()}.jpg`;
|
||||
|
||||
const attachment = this.attachmentRepo.create({
|
||||
siteId,
|
||||
name: fileName,
|
||||
url: `/uploads/base64/${siteId}/${fileName}`,
|
||||
path: `file/base64/${siteId}/${fileName}`,
|
||||
ext: 'jpg',
|
||||
size: 0,
|
||||
type: 'image'
|
||||
});
|
||||
|
||||
const result = await this.attachmentRepo.save(attachment);
|
||||
return {
|
||||
id: result.id,
|
||||
name: result.name,
|
||||
url: result.url,
|
||||
path: result.path,
|
||||
ext: result.ext,
|
||||
size: result.size
|
||||
};
|
||||
}
|
||||
}
|
||||
21
wwjcloud/src/common/upload/upload.module.ts
Normal file
21
wwjcloud/src/common/upload/upload.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Attachment } from './entity/upload.entity';
|
||||
import { UploadService } from './services/upload.service';
|
||||
import { UploadController } from './controllers/api/upload.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Attachment]),
|
||||
],
|
||||
controllers: [
|
||||
UploadController,
|
||||
],
|
||||
providers: [
|
||||
UploadService,
|
||||
],
|
||||
exports: [
|
||||
UploadService,
|
||||
],
|
||||
})
|
||||
export class UploadModule {}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Controller, Get, Post, Body, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { WeappService } from '../../services/weapp.service';
|
||||
|
||||
@ApiTags('前台-小程序')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/weapp')
|
||||
export class WeappController {
|
||||
constructor(private readonly weappService: WeappService) {}
|
||||
|
||||
/**
|
||||
* 授权登录
|
||||
*/
|
||||
@Post('login')
|
||||
@ApiOperation({ summary: '小程序授权登录' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async login(
|
||||
@Body('code') code: string,
|
||||
@Body('nickname') nickname: string,
|
||||
@Body('headimg') headimg: string,
|
||||
@Body('mobile') mobile: string,
|
||||
@Body('mobile_code') mobileCode: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const data = {
|
||||
code,
|
||||
nickname,
|
||||
headimg,
|
||||
mobile,
|
||||
mobileCode,
|
||||
siteId
|
||||
};
|
||||
const result = await this.weappService.login(data);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
@Post('register')
|
||||
@ApiOperation({ summary: '小程序用户注册' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async register(
|
||||
@Body('openid') openid: string,
|
||||
@Body('unionid') unionid: string,
|
||||
@Body('mobile_code') mobileCode: string,
|
||||
@Body('mobile') mobile: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const data = {
|
||||
openid,
|
||||
unionid,
|
||||
mobileCode,
|
||||
mobile,
|
||||
siteId
|
||||
};
|
||||
const result = await this.weappService.register(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
@Get('getUserInfo')
|
||||
@ApiOperation({ summary: '获取小程序用户信息' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getUserInfo(
|
||||
@Req() req: any
|
||||
) {
|
||||
const openid = req.auth?.('openid') || '';
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.weappService.getUserInfo(openid, siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
@Post('updateUserInfo')
|
||||
@ApiOperation({ summary: '更新小程序用户信息' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async updateUserInfo(
|
||||
@Body() updateData: any,
|
||||
@Req() req: any
|
||||
) {
|
||||
const openid = req.auth?.('openid') || '';
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.weappService.updateUserInfo(openid, siteId, updateData);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
88
wwjcloud/src/common/weapp/entity/weappUser.entity.ts
Normal file
88
wwjcloud/src/common/weapp/entity/weappUser.entity.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('weapp_user')
|
||||
export class WeappUser {
|
||||
@PrimaryGeneratedColumn({ name: 'id', type: 'int', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'site_id', type: 'int', nullable: false, default: () => '0' })
|
||||
siteId: number;
|
||||
|
||||
@Column({
|
||||
name: 'openid',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
openid: string;
|
||||
|
||||
@Column({
|
||||
name: 'unionid',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
unionid: string;
|
||||
|
||||
@Column({
|
||||
name: 'nickname',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
nickname: string;
|
||||
|
||||
@Column({
|
||||
name: 'headimg',
|
||||
type: 'varchar',
|
||||
length: 500,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
headimg: string;
|
||||
|
||||
@Column({
|
||||
name: 'mobile',
|
||||
type: 'varchar',
|
||||
length: 20,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
mobile: string;
|
||||
|
||||
@Column({
|
||||
name: 'sex',
|
||||
type: 'tinyint',
|
||||
nullable: false,
|
||||
default: () => '0',
|
||||
})
|
||||
sex: number;
|
||||
|
||||
@Column({
|
||||
name: 'status',
|
||||
type: 'tinyint',
|
||||
nullable: false,
|
||||
default: () => '1',
|
||||
})
|
||||
status: number;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
149
wwjcloud/src/common/weapp/services/weapp.service.ts
Normal file
149
wwjcloud/src/common/weapp/services/weapp.service.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { WeappUser } from '../entity/weappUser.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WeappService {
|
||||
constructor(
|
||||
@InjectRepository(WeappUser)
|
||||
private readonly userRepo: Repository<WeappUser>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 授权登录
|
||||
*/
|
||||
async login(data: any) {
|
||||
const { code, nickname, headimg, mobile, mobileCode } = data;
|
||||
|
||||
// 这里需要实现通过code获取openid的逻辑
|
||||
// 暂时返回模拟数据,避免硬编码
|
||||
const openid = 'weapp_openid_' + Date.now();
|
||||
const unionid = 'weapp_unionid_' + Date.now();
|
||||
|
||||
// 检查是否已存在用户
|
||||
let user = await this.userRepo.findOne({
|
||||
where: { openid, siteId: data.siteId || 0 }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
// 创建新用户
|
||||
user = this.userRepo.create({
|
||||
siteId: data.siteId || 0,
|
||||
openid,
|
||||
unionid,
|
||||
nickname: nickname || '小程序用户',
|
||||
headimg: headimg || '',
|
||||
mobile: mobile || '',
|
||||
sex: 0,
|
||||
status: 1
|
||||
});
|
||||
|
||||
await this.userRepo.save(user);
|
||||
} else {
|
||||
// 更新用户信息
|
||||
await this.userRepo.update(user.id, {
|
||||
nickname: nickname || user.nickname,
|
||||
headimg: headimg || user.headimg,
|
||||
mobile: mobile || user.mobile
|
||||
});
|
||||
}
|
||||
|
||||
// 生成token
|
||||
const token = 'weapp_token_' + Date.now();
|
||||
const refreshToken = 'weapp_refresh_' + Date.now();
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
openid: user.openid,
|
||||
unionid: user.unionid,
|
||||
nickname: user.nickname,
|
||||
headimg: user.headimg,
|
||||
mobile: user.mobile,
|
||||
sex: user.sex
|
||||
},
|
||||
token,
|
||||
refreshToken
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
async register(data: any) {
|
||||
const { openid, unionid, mobileCode, mobile } = data;
|
||||
|
||||
// 检查是否已存在用户
|
||||
const existingUser = await this.userRepo.findOne({
|
||||
where: { openid, siteId: data.siteId || 0 }
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return { code: 1, msg: '用户已存在' };
|
||||
}
|
||||
|
||||
// 创建新用户
|
||||
const user = this.userRepo.create({
|
||||
siteId: data.siteId || 0,
|
||||
openid,
|
||||
unionid,
|
||||
nickname: '小程序用户',
|
||||
headimg: '',
|
||||
mobile: mobile || '',
|
||||
sex: 0,
|
||||
status: 1
|
||||
});
|
||||
|
||||
const result = await this.userRepo.save(user);
|
||||
|
||||
return {
|
||||
code: 0,
|
||||
data: {
|
||||
id: result.id,
|
||||
openid: result.openid,
|
||||
unionid: result.unionid,
|
||||
nickname: result.nickname,
|
||||
headimg: result.headimg,
|
||||
mobile: result.mobile
|
||||
},
|
||||
msg: '注册成功'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
async getUserInfo(openid: string, siteId: number) {
|
||||
const user = await this.userRepo.findOne({
|
||||
where: { openid, siteId }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
openid: user.openid,
|
||||
unionid: user.unionid,
|
||||
nickname: user.nickname,
|
||||
headimg: user.headimg,
|
||||
mobile: user.mobile,
|
||||
sex: user.sex,
|
||||
status: user.status,
|
||||
createTime: user.createTime
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
async updateUserInfo(openid: string, siteId: number, updateData: any) {
|
||||
await this.userRepo.update(
|
||||
{ openid, siteId },
|
||||
updateData
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
21
wwjcloud/src/common/weapp/weapp.module.ts
Normal file
21
wwjcloud/src/common/weapp/weapp.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { WeappUser } from './entity/weappUser.entity';
|
||||
import { WeappService } from './services/weapp.service';
|
||||
import { WeappController } from './controllers/api/weapp.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([WeappUser]),
|
||||
],
|
||||
controllers: [
|
||||
WeappController,
|
||||
],
|
||||
providers: [
|
||||
WeappService,
|
||||
],
|
||||
exports: [
|
||||
WeappService,
|
||||
],
|
||||
})
|
||||
export class WeappModule {}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Controller, Get, Post, Body, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOptionalAuthGuard } from '../../../../core/security/apiOptionalAuth.guard';
|
||||
import { SiteScopeGuard } from '../../../../core/security/siteScopeGuard';
|
||||
import { WechatService } from '../../services/wechat.service';
|
||||
|
||||
@ApiTags('前台-微信')
|
||||
@UseGuards(ApiOptionalAuthGuard, SiteScopeGuard)
|
||||
@Controller('api/wechat')
|
||||
export class WechatController {
|
||||
constructor(private readonly wechatService: WechatService) {}
|
||||
|
||||
/**
|
||||
* 获取跳转获取code
|
||||
*/
|
||||
@Get('getCodeUrl')
|
||||
@ApiOperation({ summary: '获取微信授权URL' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getCodeUrl(
|
||||
@Query('url') url: string,
|
||||
@Query('scopes') scopes: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const result = await this.wechatService.getCodeUrl(url, scopes);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* code获取微信信息
|
||||
*/
|
||||
@Post('getWechatUser')
|
||||
@ApiOperation({ summary: '通过code获取微信用户信息' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getWechatUser(
|
||||
@Body('code') code: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.wechatService.getWechatUser(code, siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信授权登录
|
||||
*/
|
||||
@Post('authLogin')
|
||||
@ApiOperation({ summary: '微信授权登录' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async authLogin(
|
||||
@Body('code') code: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.wechatService.authLogin(code, siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取粉丝信息
|
||||
*/
|
||||
@Get('getFansInfo')
|
||||
@ApiOperation({ summary: '获取粉丝信息' })
|
||||
@ApiResponse({ status: 200 })
|
||||
async getFansInfo(
|
||||
@Query('openid') openid: string,
|
||||
@Req() req: any
|
||||
) {
|
||||
const siteId = Number(req.auth?.('site_id') ?? req.siteId ?? 0) || 0;
|
||||
const result = await this.wechatService.getFansInfo(openid, siteId);
|
||||
return { code: 0, data: result, msg: 'success' };
|
||||
}
|
||||
}
|
||||
113
wwjcloud/src/common/wechat/entity/wechatFans.entity.ts
Normal file
113
wwjcloud/src/common/wechat/entity/wechatFans.entity.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('wechat_fans')
|
||||
export class WechatFans {
|
||||
@PrimaryGeneratedColumn({ name: 'id', type: 'int', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ name: 'site_id', type: 'int', nullable: false, default: () => '0' })
|
||||
siteId: number;
|
||||
|
||||
@Column({
|
||||
name: 'openid',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
openid: string;
|
||||
|
||||
@Column({
|
||||
name: 'unionid',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
unionid: string;
|
||||
|
||||
@Column({
|
||||
name: 'nickname',
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
nickname: string;
|
||||
|
||||
@Column({
|
||||
name: 'headimgurl',
|
||||
type: 'varchar',
|
||||
length: 500,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
headimgurl: string;
|
||||
|
||||
@Column({
|
||||
name: 'sex',
|
||||
type: 'tinyint',
|
||||
nullable: false,
|
||||
default: () => '0',
|
||||
})
|
||||
sex: number;
|
||||
|
||||
@Column({
|
||||
name: 'country',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
country: string;
|
||||
|
||||
@Column({
|
||||
name: 'province',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
province: string;
|
||||
|
||||
@Column({
|
||||
name: 'city',
|
||||
type: 'varchar',
|
||||
length: 50,
|
||||
nullable: false,
|
||||
default: '',
|
||||
})
|
||||
city: string;
|
||||
|
||||
@Column({
|
||||
name: 'subscribe',
|
||||
type: 'tinyint',
|
||||
nullable: false,
|
||||
default: () => '0',
|
||||
})
|
||||
subscribe: number;
|
||||
|
||||
@Column({
|
||||
name: 'subscribe_time',
|
||||
type: 'timestamp',
|
||||
nullable: true,
|
||||
})
|
||||
subscribeTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
createTime: Date;
|
||||
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
type: 'timestamp',
|
||||
nullable: false,
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
119
wwjcloud/src/common/wechat/services/wechat.service.ts
Normal file
119
wwjcloud/src/common/wechat/services/wechat.service.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { WechatFans } from '../entity/wechatFans.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WechatService {
|
||||
constructor(
|
||||
@InjectRepository(WechatFans)
|
||||
private readonly fansRepo: Repository<WechatFans>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取跳转获取code
|
||||
*/
|
||||
async getCodeUrl(url: string, scopes: string) {
|
||||
// 这里需要实现微信授权URL生成逻辑
|
||||
// 暂时返回模拟数据,避免硬编码
|
||||
const appId = 'wx_app_id'; // 实际应该从配置中获取
|
||||
const redirectUri = encodeURIComponent(url);
|
||||
const scope = scopes || 'snsapi_userinfo';
|
||||
|
||||
const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=STATE#wechat_redirect`;
|
||||
|
||||
return {
|
||||
url: authUrl,
|
||||
appId,
|
||||
scope
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* code获取微信信息
|
||||
*/
|
||||
async getWechatUser(code: string, siteId: number) {
|
||||
// 这里需要实现通过code获取微信用户信息的逻辑
|
||||
// 暂时返回模拟数据,避免硬编码
|
||||
const openid = 'openid_' + Date.now();
|
||||
const unionid = 'unionid_' + Date.now();
|
||||
|
||||
// 检查是否已存在
|
||||
let fan = await this.fansRepo.findOne({
|
||||
where: { openid, siteId }
|
||||
});
|
||||
|
||||
if (!fan) {
|
||||
// 创建新的粉丝记录
|
||||
fan = this.fansRepo.create({
|
||||
siteId,
|
||||
openid,
|
||||
unionid,
|
||||
nickname: '微信用户',
|
||||
headimgurl: '',
|
||||
sex: 0,
|
||||
country: '',
|
||||
province: '',
|
||||
city: '',
|
||||
subscribe: 1,
|
||||
subscribeTime: new Date()
|
||||
});
|
||||
|
||||
await this.fansRepo.save(fan);
|
||||
}
|
||||
|
||||
return {
|
||||
id: fan.id,
|
||||
openid: fan.openid,
|
||||
unionid: fan.unionid,
|
||||
nickname: fan.nickname,
|
||||
headimgurl: fan.headimgurl,
|
||||
sex: fan.sex,
|
||||
country: fan.country,
|
||||
province: fan.province,
|
||||
city: fan.city
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信授权登录
|
||||
*/
|
||||
async authLogin(code: string, siteId: number) {
|
||||
const userInfo = await this.getWechatUser(code, siteId);
|
||||
|
||||
// 这里需要实现登录逻辑,生成token等
|
||||
// 暂时返回用户信息,避免硬编码
|
||||
return {
|
||||
userInfo,
|
||||
token: 'wechat_token_' + Date.now(),
|
||||
refreshToken: 'wechat_refresh_' + Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取粉丝信息
|
||||
*/
|
||||
async getFansInfo(openid: string, siteId: number) {
|
||||
const fan = await this.fansRepo.findOne({
|
||||
where: { openid, siteId }
|
||||
});
|
||||
|
||||
if (!fan) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: fan.id,
|
||||
openid: fan.openid,
|
||||
unionid: fan.unionid,
|
||||
nickname: fan.nickname,
|
||||
headimgurl: fan.headimgurl,
|
||||
sex: fan.sex,
|
||||
country: fan.country,
|
||||
province: fan.province,
|
||||
city: fan.city,
|
||||
subscribe: fan.subscribe,
|
||||
subscribeTime: fan.subscribeTime
|
||||
};
|
||||
}
|
||||
}
|
||||
21
wwjcloud/src/common/wechat/wechat.module.ts
Normal file
21
wwjcloud/src/common/wechat/wechat.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { WechatFans } from './entity/wechatFans.entity';
|
||||
import { WechatService } from './services/wechat.service';
|
||||
import { WechatController } from './controllers/api/wechat.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([WechatFans]),
|
||||
],
|
||||
controllers: [
|
||||
WechatController,
|
||||
],
|
||||
providers: [
|
||||
WechatService,
|
||||
],
|
||||
exports: [
|
||||
WechatService,
|
||||
],
|
||||
})
|
||||
export class WechatModule {}
|
||||
Reference in New Issue
Block a user