feat(站点): 实现站点管理功能
添加站点实体、服务层和控制器,支持站点的CRUD操作 同步配置中的站点信息到数据库 提供站点禁用/启用功能
This commit is contained in:
parent
ec6a8c3154
commit
c75d620516
|
|
@ -33,6 +33,7 @@ import { Customer } from '../entity/customer.entity';
|
||||||
import { DeviceWhitelist } from '../entity/device_whitelist';
|
import { DeviceWhitelist } from '../entity/device_whitelist';
|
||||||
import { AuthCode } from '../entity/auth_code';
|
import { AuthCode } from '../entity/auth_code';
|
||||||
import { Subscription } from '../entity/subscription.entity';
|
import { Subscription } from '../entity/subscription.entity';
|
||||||
|
import { Site } from '../entity/site.entity';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// use for cookie sign key, should change to your own and keep security
|
// use for cookie sign key, should change to your own and keep security
|
||||||
|
|
@ -74,6 +75,7 @@ export default {
|
||||||
DeviceWhitelist,
|
DeviceWhitelist,
|
||||||
AuthCode,
|
AuthCode,
|
||||||
Subscription,
|
Subscription,
|
||||||
|
Site,
|
||||||
],
|
],
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
logging: false,
|
logging: false,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import * as crossDomain from '@midwayjs/cross-domain';
|
||||||
import * as cron from '@midwayjs/cron';
|
import * as cron from '@midwayjs/cron';
|
||||||
import * as jwt from '@midwayjs/jwt';
|
import * as jwt from '@midwayjs/jwt';
|
||||||
import { USER_KEY } from './decorator/user.decorator';
|
import { USER_KEY } from './decorator/user.decorator';
|
||||||
|
import { SiteService } from './service/site.service';
|
||||||
import { AuthMiddleware } from './middleware/auth.middleware';
|
import { AuthMiddleware } from './middleware/auth.middleware';
|
||||||
|
|
||||||
@Configuration({
|
@Configuration({
|
||||||
|
|
@ -45,6 +46,9 @@ export class MainConfiguration {
|
||||||
@Inject()
|
@Inject()
|
||||||
jwtService: jwt.JwtService; // 注入 JwtService 实例
|
jwtService: jwt.JwtService; // 注入 JwtService 实例
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
siteService: SiteService;
|
||||||
|
|
||||||
async onReady() {
|
async onReady() {
|
||||||
// add middleware
|
// add middleware
|
||||||
this.app.useMiddleware([ReportMiddleware, AuthMiddleware]);
|
this.app.useMiddleware([ReportMiddleware, AuthMiddleware]);
|
||||||
|
|
@ -74,5 +78,8 @@ export class MainConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sites = this.app.getConfig('wpSite') || [];
|
||||||
|
await this.siteService.syncFromConfig(sites);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,75 @@
|
||||||
import { Config, Controller, Get } from '@midwayjs/core';
|
import { Body, Controller, Get, Inject, Param, Put, Post, Query } from '@midwayjs/core';
|
||||||
import { ApiOkResponse } from '@midwayjs/swagger';
|
import { ApiOkResponse } from '@midwayjs/swagger';
|
||||||
import { WpSitesResponse } from '../dto/reponse.dto';
|
import { WpSitesResponse } from '../dto/reponse.dto';
|
||||||
import { successResponse } from '../utils/response.util';
|
import { errorResponse, successResponse } from '../utils/response.util';
|
||||||
import { WpSite } from '../interface';
|
import { SiteService } from '../service/site.service';
|
||||||
|
import { CreateSiteDTO, DisableSiteDTO, QuerySiteDTO, UpdateSiteDTO } from '../dto/site.dto';
|
||||||
|
|
||||||
@Controller('/site')
|
@Controller('/site')
|
||||||
export class SiteController {
|
export class SiteController {
|
||||||
@Config('wpSite')
|
@Inject()
|
||||||
sites: WpSite[];
|
siteService: SiteService;
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({ description: '关联网站', type: WpSitesResponse })
|
||||||
description: '关联网站',
|
|
||||||
type: WpSitesResponse,
|
|
||||||
})
|
|
||||||
@Get('/all')
|
@Get('/all')
|
||||||
async all() {
|
async all() {
|
||||||
return successResponse(
|
try {
|
||||||
this.sites.map(v => ({
|
const { items } = await this.siteService.list({ current: 1, pageSize: 1000, isDisabled: false });
|
||||||
id: v.id,
|
return successResponse(items.map((v: any) => ({ id: v.id, siteName: v.siteName })));
|
||||||
siteName: v.siteName,
|
} catch (error) {
|
||||||
}))
|
return errorResponse(error?.message || '获取失败');
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/create')
|
||||||
|
async create(@Body() body: CreateSiteDTO) {
|
||||||
|
try {
|
||||||
|
await this.siteService.create(body);
|
||||||
|
return successResponse(true);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '创建失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('/update/:id')
|
||||||
|
async update(@Param('id') id: string, @Body() body: UpdateSiteDTO) {
|
||||||
|
try {
|
||||||
|
await this.siteService.update(Number(id), body);
|
||||||
|
return successResponse(true);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '更新失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/get/:id')
|
||||||
|
async get(@Param('id') id: string) {
|
||||||
|
try {
|
||||||
|
const data = await this.siteService.get(Number(id), false);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '获取失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/list')
|
||||||
|
async list(@Query() query: QuerySiteDTO) {
|
||||||
|
try {
|
||||||
|
const data = await this.siteService.list(query, false);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '获取失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量查询改为使用 /site/list?ids=1,2,3
|
||||||
|
|
||||||
|
@Put('/disable/:id')
|
||||||
|
async disable(@Param('id') id: string, @Body() body: DisableSiteDTO) {
|
||||||
|
try {
|
||||||
|
await this.siteService.disable(Number(id), body.disabled);
|
||||||
|
return successResponse(true);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '更新失败');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export class SiteConfig {
|
||||||
|
|
||||||
@ApiProperty({ description: '站点 URL' })
|
@ApiProperty({ description: '站点 URL' })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
wpApiUrl: string;
|
apiUrl: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '站点 rest key' })
|
@ApiProperty({ description: '站点 rest key' })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
|
|
@ -22,11 +22,61 @@ export class SiteConfig {
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
siteName: string;
|
siteName: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '站点邮箱' })
|
@ApiProperty({ description: '平台类型', enum: ['woocommerce', 'shopyy'] })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string().valid('woocommerce', 'shopyy'))
|
||||||
email?: string;
|
type: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '站点邮箱密码' })
|
@ApiProperty({ description: 'SKU 前缀' })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
emailPswd?: string;
|
skuPrefix: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateSiteDTO {
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
apiUrl?: string;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
consumerKey?: string;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
consumerSecret?: string;
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
siteName: string;
|
||||||
|
@Rule(RuleType.string().valid('woocommerce', 'shopyy').optional())
|
||||||
|
type?: string;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
skuPrefix?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateSiteDTO {
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
apiUrl?: string;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
consumerKey?: string;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
consumerSecret?: string;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
siteName?: string;
|
||||||
|
@Rule(RuleType.boolean().optional())
|
||||||
|
isDisabled?: boolean;
|
||||||
|
@Rule(RuleType.string().valid('woocommerce', 'shopyy').optional())
|
||||||
|
type?: string;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
skuPrefix?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class QuerySiteDTO {
|
||||||
|
@Rule(RuleType.number().optional())
|
||||||
|
current?: number;
|
||||||
|
@Rule(RuleType.number().optional())
|
||||||
|
pageSize?: number;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
keyword?: string;
|
||||||
|
@Rule(RuleType.boolean().optional())
|
||||||
|
isDisabled?: boolean;
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
ids?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DisableSiteDTO {
|
||||||
|
@Rule(RuleType.boolean())
|
||||||
|
disabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity('site')
|
||||||
|
export class Site {
|
||||||
|
@PrimaryGeneratedColumn({ type: 'int' })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||||
|
apiUrl: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||||
|
consumerKey: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||||
|
consumerSecret: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255, unique: true })
|
||||||
|
siteName: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 32, default: 'woocommerce' })
|
||||||
|
type: string; // 平台类型:woocommerce | shopyy
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 64, nullable: true })
|
||||||
|
skuPrefix: string;
|
||||||
|
|
||||||
|
@Column({ type: 'tinyint', default: 0 })
|
||||||
|
isDisabled: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
|
import { Repository, Like, In } from 'typeorm';
|
||||||
|
import { Site } from '../entity/site.entity';
|
||||||
|
import { WpSite } from '../interface';
|
||||||
|
import { UpdateSiteDTO } from '../dto/site.dto';
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class SiteService {
|
||||||
|
@InjectEntityModel(Site)
|
||||||
|
siteModel: Repository<Site>;
|
||||||
|
|
||||||
|
async syncFromConfig(sites: WpSite[] = []) {
|
||||||
|
for (const s of sites) {
|
||||||
|
const exist = await this.siteModel.findOne({ where: { siteName: s.siteName } });
|
||||||
|
const payload: Partial<Site> = {
|
||||||
|
siteName: s.siteName,
|
||||||
|
apiUrl: (s as any).wpApiUrl,
|
||||||
|
consumerKey: (s as any).consumerKey,
|
||||||
|
consumerSecret: (s as any).consumerSecret,
|
||||||
|
type: 'woocommerce',
|
||||||
|
};
|
||||||
|
if (exist) await this.siteModel.update({ id: exist.id }, payload);
|
||||||
|
else await this.siteModel.insert(payload as Site);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(data: Partial<Site>) {
|
||||||
|
await this.siteModel.insert(data as Site);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string | number, data: UpdateSiteDTO) {
|
||||||
|
const payload: Partial<Site> = {
|
||||||
|
...data,
|
||||||
|
isDisabled:
|
||||||
|
data.isDisabled === undefined
|
||||||
|
? undefined
|
||||||
|
: data.isDisabled
|
||||||
|
? 1
|
||||||
|
: 0,
|
||||||
|
} as any;
|
||||||
|
await this.siteModel.update({ id: Number(id) }, payload);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(id: string | number, includeSecret = false) {
|
||||||
|
const s = await this.siteModel.findOne({ where: { id: Number(id) } });
|
||||||
|
if (!s) return null;
|
||||||
|
if (includeSecret) return s;
|
||||||
|
const { consumerKey, consumerSecret, emailPswd, ...rest } = s as any;
|
||||||
|
return rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
async list(param: { current?: number; pageSize?: number; keyword?: string; isDisabled?: boolean; ids?: string }, includeSecret = false) {
|
||||||
|
const { current = 1, pageSize = 10, keyword, isDisabled, ids } = (param || {}) as any;
|
||||||
|
const where: any = {};
|
||||||
|
if (keyword) where.siteName = Like(`%${keyword}%`);
|
||||||
|
if (typeof isDisabled === 'boolean') where.isDisabled = isDisabled ? 1 : 0;
|
||||||
|
if (ids) {
|
||||||
|
const numIds = String(ids)
|
||||||
|
.split(',')
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((i) => Number(i))
|
||||||
|
.filter((v) => !Number.isNaN(v));
|
||||||
|
if (numIds.length > 0) where.id = In(numIds);
|
||||||
|
}
|
||||||
|
const [items, total] = await this.siteModel.findAndCount({ where, skip: (current - 1) * pageSize, take: pageSize });
|
||||||
|
const data = includeSecret ? items : items.map((s: any) => {
|
||||||
|
const { consumerKey, consumerSecret, ...rest } = s;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
return { items: data, total, current, pageSize };
|
||||||
|
}
|
||||||
|
|
||||||
|
async disable(id: string | number, disabled: boolean) {
|
||||||
|
await this.siteModel.update({ id: Number(id) }, { isDisabled: disabled ? 1 : 0 });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue