From ca24ab9922f6468ce7e3389d6e878f4b364a675e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=96=E7=A0=81=E7=8C=BF?= Date: Thu, 18 Sep 2025 01:50:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20=E5=BE=AE=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E6=8B=86=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + front-end/app-product/README.md | 11 +- .../src/shared/types/product-federation.d.ts | 3 + front-end/main-app/package.json | 8 +- .../main-app/src/app/di/productModule.ts | 14 +- front-end/main-app/src/app/router/index.ts | 2 +- .../services/ProductApplicationService.ts | 22 --- .../product/domain/entities/Product.ts | 141 ------------------ .../product/domain/enums/ErrorEnums.ts | 7 - .../domains/product/domain/events/README.md | 1 - .../product/domain/type/ProductType.ts | 9 -- .../product/domain/value-objects/README.md | 1 - .../product/infr/api/HttpProductRepository.ts | 26 ---- .../domains/product/infr/config/HttpConfig.ts | 11 -- .../ports/repositories/ProductRepository.ts | 20 --- .../src/features/product/assets/README.md | 1 - .../src/features/product/components/README.md | 1 - .../features/product/composables/README.md | 1 - .../main-app/src/features/product/index.ts | 0 .../src/features/product/router/index.ts | 17 --- .../features/product/views/ProductDetail.tsx | 53 ------- .../features/product/views/ProductList.tsx | 53 ------- .../main-app/src/shared/config/HttpConfig.ts | 4 +- .../main-app/src/shared/config/ThemeConfig.ts | 2 +- front-end/main-app/src/shared/config/index.ts | 4 - .../src/shared/utils/UrlCheckUtils.ts | 2 +- front-end/main-app/vite.config.ts | 27 ++++ 27 files changed, 59 insertions(+), 384 deletions(-) create mode 100644 front-end/app-product/src/shared/types/product-federation.d.ts delete mode 100644 front-end/main-app/src/domains/product/application/services/ProductApplicationService.ts delete mode 100644 front-end/main-app/src/domains/product/domain/entities/Product.ts delete mode 100644 front-end/main-app/src/domains/product/domain/enums/ErrorEnums.ts delete mode 100644 front-end/main-app/src/domains/product/domain/events/README.md delete mode 100644 front-end/main-app/src/domains/product/domain/type/ProductType.ts delete mode 100644 front-end/main-app/src/domains/product/domain/value-objects/README.md delete mode 100644 front-end/main-app/src/domains/product/infr/api/HttpProductRepository.ts delete mode 100644 front-end/main-app/src/domains/product/infr/config/HttpConfig.ts delete mode 100644 front-end/main-app/src/domains/product/ports/repositories/ProductRepository.ts delete mode 100644 front-end/main-app/src/features/product/assets/README.md delete mode 100644 front-end/main-app/src/features/product/components/README.md delete mode 100644 front-end/main-app/src/features/product/composables/README.md delete mode 100644 front-end/main-app/src/features/product/index.ts delete mode 100644 front-end/main-app/src/features/product/router/index.ts delete mode 100644 front-end/main-app/src/features/product/views/ProductDetail.tsx delete mode 100644 front-end/main-app/src/features/product/views/ProductList.tsx delete mode 100644 front-end/main-app/src/shared/config/index.ts diff --git a/README.md b/README.md index 2fdc7c0..5a7b56c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ + + ## DDD领域驱动设计的微服务前端案例 > 持续开发中,及时拉取代码,文档还不够完善,先凑合看!!目前只是demo,代码还会进行大规模封装细化和改造! diff --git a/front-end/app-product/README.md b/front-end/app-product/README.md index abfa02f..5d3339b 100644 --- a/front-end/app-product/README.md +++ b/front-end/app-product/README.md @@ -1 +1,10 @@ -# 临时占位,上传空文件夹,保存文件结构用,后面删除 \ No newline at end of file +npm install +npm run build +npm run serve + + +构建与部署: + +- 在构建生产环境时,先构建主模块,并将其产出(特别是 remoteEntry.js 和对应的资产文件)部署到服务器,并获取其 URL。 +- 然后,在子模块的 vite.config.ts 中,将 remotes 的 URL 更新为生产环境主模块 remoteEntry.js 的绝对 URL。 +- 最后再构建子模块。 \ No newline at end of file diff --git a/front-end/app-product/src/shared/types/product-federation.d.ts b/front-end/app-product/src/shared/types/product-federation.d.ts new file mode 100644 index 0000000..3d9d762 --- /dev/null +++ b/front-end/app-product/src/shared/types/product-federation.d.ts @@ -0,0 +1,3 @@ +declare module 'mainApp/productService' { + export const product: any; +} \ No newline at end of file diff --git a/front-end/main-app/package.json b/front-end/main-app/package.json index 7b3d150..b7f3dd4 100644 --- a/front-end/main-app/package.json +++ b/front-end/main-app/package.json @@ -7,10 +7,10 @@ "node": "^20.19.0 || >=22.12.0" }, "scripts": { - "dev": "vite", - "build": "run-p type-check \"build-only {@}\" --", - "preview": "vite preview", - "build-only": "vite build", + "dev": "vite --port 5000 --strictPort", + "serve": "vite preview --port 5000 --strictPort", + "build": "vite build", + "all": "npm run build && npm run serve", "type-check": "vue-tsc --build" }, "dependencies": { diff --git a/front-end/main-app/src/app/di/productModule.ts b/front-end/main-app/src/app/di/productModule.ts index c234587..e45c18a 100644 --- a/front-end/main-app/src/app/di/productModule.ts +++ b/front-end/main-app/src/app/di/productModule.ts @@ -1,7 +1,9 @@ -import { ProductApplicationService } from "../../domains/product/application/services/ProductApplicationService"; -import { HttpProductRepository } from "../../domains/product/infr/api/HttpProductRepository"; -import { ProductRepository } from "../../domains/product/ports/repositories/ProductRepository"; - +// 从商品联邦模块导入领域层资源 +import { + ProductApplicationService, + HttpProductRepository, + ProductRepository +} from 'productModule/ProductDomain'; /** * 商品领域的依赖注入配置 @@ -21,7 +23,7 @@ import { ProductRepository } from "../../domains/product/ports/repositories/Prod * productModule.bindProductRepository = () => new MockProductRepository(); * 这样测试就可以脱离真实 API,使用预设的模拟数据。 */ -export const productModule = { +export const product = { // 绑定 仓储抽象接口 到 HTTP具体实现 bindProductRepository: (): ProductRepository => { @@ -32,7 +34,7 @@ export const productModule = { // 创建应用服务实例 createProductApplicationService: (): ProductApplicationService => { return new ProductApplicationService( - productModule.bindProductRepository() + product.bindProductRepository() ); } } \ No newline at end of file diff --git a/front-end/main-app/src/app/router/index.ts b/front-end/main-app/src/app/router/index.ts index f68c1e1..6230ca0 100644 --- a/front-end/main-app/src/app/router/index.ts +++ b/front-end/main-app/src/app/router/index.ts @@ -1,5 +1,5 @@ import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' -import { productRouter } from '@features/product/router' +import { productRouter } from 'productModule/ProductRouter' const baseRouter: Array = [ { diff --git a/front-end/main-app/src/domains/product/application/services/ProductApplicationService.ts b/front-end/main-app/src/domains/product/application/services/ProductApplicationService.ts deleted file mode 100644 index aa42975..0000000 --- a/front-end/main-app/src/domains/product/application/services/ProductApplicationService.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ErrorText } from "../../domain/enums/ErrorEnums"; -import { ProductRepository } from "../../ports/repositories/ProductRepository"; - -/** - * 商品应用服务 - 协调领域对象完成业务用例 - * 应用服务是领域层的外观,封装了领域逻辑的调用 - */ -export class ProductApplicationService { - // 依赖注入仓储接口,而不是具体实现 - constructor(private productRepository: ProductRepository) { } - - async getProductList() { - return await this.productRepository.findAll(); - } - - async getProductInfo(id: number) { - if (!id) { - throw new Error(ErrorText.IdNotNull) - } - return await this.productRepository.findById(id); - } -} \ No newline at end of file diff --git a/front-end/main-app/src/domains/product/domain/entities/Product.ts b/front-end/main-app/src/domains/product/domain/entities/Product.ts deleted file mode 100644 index 236f464..0000000 --- a/front-end/main-app/src/domains/product/domain/entities/Product.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { ErrorText } from "../enums/ErrorEnums" - -/** - * 商品实体 - 包含商品的核心属性和行为 - * 在DDD中,实体具有唯一标识和生命周期 - */ -export class Product { - private readonly _id: number; - private _name: string; - private _description: string; - private _price: number; - private _imageUrl: string; - private _stock: number; - private _categoryId: number; - - constructor( - id: number, - name: string, - description: string, - price: number, - imageUrl: string, - stock: number, - categoryId: number, - ) { - this._id = id; - this._name = name; - this._description = description; - this._price = price; - this._imageUrl = imageUrl; - this._stock = stock; - this._categoryId = categoryId; - } - - // 实体唯一标识 - get id(): number { - return this._id; - } - - public get name(): string { - return this._name; - } - public set name(value: string) { - this._name = value; - } - - public get description(): string { - return this._description; - } - public set description(value: string) { - this._description = value; - } - - - public get price(): number { - return this._price; - } - public set price(value: number) { - if (value < 0) { - throw new Error(ErrorText.PriceNotNegativein); - } - this._price = value; - } - - public get imageUrl(): string { - return this._imageUrl; - } - public set imageUrl(value: string) { - this._imageUrl = value; - } - - public get stock(): number { - return this._stock; - } - public set stock(value: number) { - if (value < 0) { - throw new Error(ErrorText.StockNotNegativein) - } - this._stock = value; - } - - public get categoryId(): number { - return this._categoryId; - } - public set categoryId(value: number) { - this._categoryId = value; - } - - /** - * 检查商品是否有库存 - */ - hasStock() { - return this._stock > 0 - } - - /** - * 减少商品库存 - * @param quantity 减少的数量 - */ - reduceStock(quantity: number): void { - if (quantity <= 0) { - throw new Error(ErrorText.StockIsGreaterThanZero) - } - - if (this._stock < quantity) { - throw new Error(ErrorText.LowStock) - } - - this._stock -= quantity; - } - - /** - * Class类转化为普通json数据 - */ - toDTO(): Record { - return { - id: this._id, - name: this._name, - description: this._description, - price: this._price, - imageUrl: this._imageUrl, - stock: this._stock, - categoryId: this._categoryId - } - } - - /** - * 从json数据创建Product类实例 - * 转化为领域实体 Product - */ - static fromDTO(data: Record): Product { - return new Product( - data.id, - data.name, - data.description, - data.price, - data.imageUrl, - data.stock, - data.categoryId, - ); - } -} \ No newline at end of file diff --git a/front-end/main-app/src/domains/product/domain/enums/ErrorEnums.ts b/front-end/main-app/src/domains/product/domain/enums/ErrorEnums.ts deleted file mode 100644 index c766cc4..0000000 --- a/front-end/main-app/src/domains/product/domain/enums/ErrorEnums.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum ErrorText { - PriceNotNegativein = "商品 价格 不能为负数", - StockNotNegativein = "商品 库存 不能为负数", - StockIsGreaterThanZero = "减少的库存数量必须大于0", - LowStock = "库存不足", - IdNotNull = "商品Id不能为空" -} \ No newline at end of file diff --git a/front-end/main-app/src/domains/product/domain/events/README.md b/front-end/main-app/src/domains/product/domain/events/README.md deleted file mode 100644 index abfa02f..0000000 --- a/front-end/main-app/src/domains/product/domain/events/README.md +++ /dev/null @@ -1 +0,0 @@ -# 临时占位,上传空文件夹,保存文件结构用,后面删除 \ No newline at end of file diff --git a/front-end/main-app/src/domains/product/domain/type/ProductType.ts b/front-end/main-app/src/domains/product/domain/type/ProductType.ts deleted file mode 100644 index 8669e63..0000000 --- a/front-end/main-app/src/domains/product/domain/type/ProductType.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface ProductType { - id: number, - name: string, - description: string, - price: number, - imageUrl: string, - stock: number, - categoryId: number, -} \ No newline at end of file diff --git a/front-end/main-app/src/domains/product/domain/value-objects/README.md b/front-end/main-app/src/domains/product/domain/value-objects/README.md deleted file mode 100644 index abfa02f..0000000 --- a/front-end/main-app/src/domains/product/domain/value-objects/README.md +++ /dev/null @@ -1 +0,0 @@ -# 临时占位,上传空文件夹,保存文件结构用,后面删除 \ No newline at end of file diff --git a/front-end/main-app/src/domains/product/infr/api/HttpProductRepository.ts b/front-end/main-app/src/domains/product/infr/api/HttpProductRepository.ts deleted file mode 100644 index a73b072..0000000 --- a/front-end/main-app/src/domains/product/infr/api/HttpProductRepository.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Product } from "../../domain/entities/Product"; -import { ProductType } from "../../domain/type/ProductType"; -import { ProductRepository } from "../../ports/repositories/ProductRepository"; -import http from "@shared/infra/AxiosInfra"; -import HttpConfig from "../config/HttpConfig"; - -/** - * HTTP商品仓储实现 - 实现商品数据的HTTP访问 - * 这是基础设施层,负责具体的数据获取实现 - */ -export class HttpProductRepository implements ProductRepository { - - async findAll(): Promise> { - const result: Array = await http.get({ url: HttpConfig.apiUrlList.productsListUrl }); - const items = result.map((item) => Product.fromDTO(item)) - return items; - } - - async findById(id: number): Promise { - const result: ProductType = await http.get({ - url: HttpConfig.apiUrlList.productsInfoUrl, - data: { id } - }); - return Product.fromDTO(result); - } -} \ No newline at end of file diff --git a/front-end/main-app/src/domains/product/infr/config/HttpConfig.ts b/front-end/main-app/src/domains/product/infr/config/HttpConfig.ts deleted file mode 100644 index 3fd9fc2..0000000 --- a/front-end/main-app/src/domains/product/infr/config/HttpConfig.ts +++ /dev/null @@ -1,11 +0,0 @@ -interface apiUrlListType { - productsListUrl: string - productsInfoUrl: string -} - -export default class HttpConfig { - public static apiUrlList: apiUrlListType = { - productsListUrl: '/productsList', - productsInfoUrl: '/productsInfo', - } -} diff --git a/front-end/main-app/src/domains/product/ports/repositories/ProductRepository.ts b/front-end/main-app/src/domains/product/ports/repositories/ProductRepository.ts deleted file mode 100644 index ee49c7f..0000000 --- a/front-end/main-app/src/domains/product/ports/repositories/ProductRepository.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Product } from "../../domain/entities/Product" -import { ProductType } from "../../domain/type/ProductType" - -/** - * 商品仓储接口 - 定义商品数据访问的契约 - * 在DDD中,端口定义了领域层与外部世界的交互方式 - */ -export interface ProductRepository { - - /** - * 获取商品列表 - */ - findAll(): Promise>; - - /** - * 根据ID获取商品详情 - * @param id 商品ID - */ - findById(id: number): Promise -} \ No newline at end of file diff --git a/front-end/main-app/src/features/product/assets/README.md b/front-end/main-app/src/features/product/assets/README.md deleted file mode 100644 index abfa02f..0000000 --- a/front-end/main-app/src/features/product/assets/README.md +++ /dev/null @@ -1 +0,0 @@ -# 临时占位,上传空文件夹,保存文件结构用,后面删除 \ No newline at end of file diff --git a/front-end/main-app/src/features/product/components/README.md b/front-end/main-app/src/features/product/components/README.md deleted file mode 100644 index abfa02f..0000000 --- a/front-end/main-app/src/features/product/components/README.md +++ /dev/null @@ -1 +0,0 @@ -# 临时占位,上传空文件夹,保存文件结构用,后面删除 \ No newline at end of file diff --git a/front-end/main-app/src/features/product/composables/README.md b/front-end/main-app/src/features/product/composables/README.md deleted file mode 100644 index abfa02f..0000000 --- a/front-end/main-app/src/features/product/composables/README.md +++ /dev/null @@ -1 +0,0 @@ -# 临时占位,上传空文件夹,保存文件结构用,后面删除 \ No newline at end of file diff --git a/front-end/main-app/src/features/product/index.ts b/front-end/main-app/src/features/product/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/front-end/main-app/src/features/product/router/index.ts b/front-end/main-app/src/features/product/router/index.ts deleted file mode 100644 index d72ee8c..0000000 --- a/front-end/main-app/src/features/product/router/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { RouteRecordRaw } from 'vue-router'; - - -export const productRouter: Array = [ - { - path: '/Products', - name: 'ProductList', - component: () => import('../views/ProductList.tsx'), - meta: { title: '商品列表' } - }, - { - path: '/ProductDetail', - name: 'ProductDetail', - component: () => import('../views/ProductDetail.tsx'), - meta: { title: '商品详情' } - }, -] \ No newline at end of file diff --git a/front-end/main-app/src/features/product/views/ProductDetail.tsx b/front-end/main-app/src/features/product/views/ProductDetail.tsx deleted file mode 100644 index 6c8db5e..0000000 --- a/front-end/main-app/src/features/product/views/ProductDetail.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { productModule } from '@/di/productModule'; -import { ProductType } from '@domains/product/domain/type/ProductType'; -import { defineComponent, onMounted, reactive } from 'vue' -import { useRoute } from 'vue-router' - -interface storeType { - item: ProductType - id: number -} - -export default defineComponent({ - name: "productModule-ProductDetail", - setup() { - const route = useRoute(); - const productService = productModule.createProductApplicationService(); - - const state = reactive({ - item: {} as ProductType, - id: 0 - }) - - onMounted(() => { - if (!route.query.id) { - throw new Error("缺少id参数"); - } - - state.id = Number(route.query.id) - console.log("id: ", state.id); - loadProductDetail() - }) - - const loadProductDetail = async () => { - state.item = await productService.getProductInfo(state.id); - console.log("state.item: ", state.item); - } - - return () => ( -
-

商品领域模块 - 商品详情页面

-
-

商品id:{state.id}

- {state.item.name} -
    - { - Object.entries(state.item).map(([key, value]) => -
  • {key}: {value}
  • - ) - } -
-
- ) - } -}) \ No newline at end of file diff --git a/front-end/main-app/src/features/product/views/ProductList.tsx b/front-end/main-app/src/features/product/views/ProductList.tsx deleted file mode 100644 index 736689d..0000000 --- a/front-end/main-app/src/features/product/views/ProductList.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { defineComponent, onMounted, reactive } from 'vue' -import { useRouter } from 'vue-router'; -import { productModule } from '@/di/productModule'; -import { ProductType } from '@domains/product/domain/type/ProductType'; - -interface storeType { - list: Array -} - -export default defineComponent({ - name: "productModule-ProductList", - setup() { - - const router = useRouter(); - const productService = productModule.createProductApplicationService(); - - const state = reactive({ - list: [] - }) - - - onMounted(() => { - loadProductList() - }) - - const loadProductList = async () => { - state.list = await productService.getProductList(); - console.log("state.list: ", state.list); - } - - return () => ( -
-

商品领域模块 - 商品列表页面

-
-

从Deno 后端 加载的数据,点击进详情

-
    - { - state.list.map(item => -
  • { - router.push({ - name: 'ProductDetail', - query: { id: item.id } - }); - }}> - {item.id} | {item.name} | ¥{item.price} -
  • - ) - } -
-
- ) - } -}) \ No newline at end of file diff --git a/front-end/main-app/src/shared/config/HttpConfig.ts b/front-end/main-app/src/shared/config/HttpConfig.ts index 7b8b267..8326d30 100644 --- a/front-end/main-app/src/shared/config/HttpConfig.ts +++ b/front-end/main-app/src/shared/config/HttpConfig.ts @@ -1,5 +1,5 @@ -export default class HttpConfig { +export class HttpConfig { public static DevBaseUrl: string = "http://127.0.0.1:3000/api" - public static prodBaseUrl: string = "http://127.0.0.1:9090/api/" + public static prodBaseUrl: string = "http://127.0.0.1:3000/api/" public static apiList: string = "" } diff --git a/front-end/main-app/src/shared/config/ThemeConfig.ts b/front-end/main-app/src/shared/config/ThemeConfig.ts index 1e52897..7e08a8d 100644 --- a/front-end/main-app/src/shared/config/ThemeConfig.ts +++ b/front-end/main-app/src/shared/config/ThemeConfig.ts @@ -1,4 +1,4 @@ -export default class ThemeConfig { +export class ThemeConfig { public static primaryColor: string = '#1890ff'; // 主色调 public static layoutType: 'side' | 'top' = 'side'; // 侧边栏布局 } \ No newline at end of file diff --git a/front-end/main-app/src/shared/config/index.ts b/front-end/main-app/src/shared/config/index.ts deleted file mode 100644 index fc11432..0000000 --- a/front-end/main-app/src/shared/config/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import HttpConfig from './HttpConfig'; -import ThemeConfig from './ThemeConfig'; - -export { HttpConfig, ThemeConfig }; \ No newline at end of file diff --git a/front-end/main-app/src/shared/utils/UrlCheckUtils.ts b/front-end/main-app/src/shared/utils/UrlCheckUtils.ts index eb76c89..fe8642e 100644 --- a/front-end/main-app/src/shared/utils/UrlCheckUtils.ts +++ b/front-end/main-app/src/shared/utils/UrlCheckUtils.ts @@ -1,4 +1,4 @@ -import { HttpConfig } from '@shared/config/index' +import { HttpConfig } from '@shared/config/HttpConfig' export default class UrlCheckUtils { diff --git a/front-end/main-app/vite.config.ts b/front-end/main-app/vite.config.ts index ee6957b..2f5cb90 100644 --- a/front-end/main-app/vite.config.ts +++ b/front-end/main-app/vite.config.ts @@ -4,6 +4,7 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' import vueDevTools from 'vite-plugin-vue-devtools' +import federation from '@originjs/vite-plugin-federation'; // https://vite.dev/config/ export default defineConfig({ @@ -11,7 +12,33 @@ export default defineConfig({ vue(), vueJsx(), vueDevTools(), + federation({ + name: 'mainApp', // 主应用模块名(联邦模块需通过此名引用) + remotes: { // 配置远程联邦模块地址(开发/生产需对应) + productModule: 'http://localhost:5001/assets/remoteEntry.js' + }, + exposes: { + "./shared": "./src/shared/index.ts", + "./productService": "./src/app/di/productModule.ts", + }, + shared: { + vue: { requiredVersion: '^3.5.18' }, + axios: { requiredVersion: '^1.12.2' }, + 'vue-router': { requiredVersion: '^4.0.6' }, + pinia: { requiredVersion: '^3.0.3' } + } + }) ], + build: { + target: 'esnext', + minify: false, + cssCodeSplit: true, + rollupOptions: { + output: { + minifyInternalExports: false + } + } + }, resolve: { alias: { '@': fileURLToPath(new URL('./src/app/', import.meta.url)),