diff --git a/.vscode/launch.json b/.vscode/launch.json index d53bf90..b2cf7c3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,5 +1,12 @@ { "configurations": [ + { + "type": "java", + "name": "CriticalScenarioApplication", + "request": "launch", + "mainClass": "com.yfd.business.css.CriticalScenarioApplication", + "projectName": "business-css" + }, { "type": "java", "name": "Spring Boot-PlatformApplication", diff --git a/business-css/src/main/java/com/yfd/business/css/CriticalScenarioApplication.java b/business-css/src/main/java/com/yfd/business/css/CriticalScenarioApplication.java index fde89d5..2ebca36 100644 --- a/business-css/src/main/java/com/yfd/business/css/CriticalScenarioApplication.java +++ b/business-css/src/main/java/com/yfd/business/css/CriticalScenarioApplication.java @@ -1,9 +1,27 @@ package com.yfd.business.css; +import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -@SpringBootApplication +@SpringBootApplication( + scanBasePackages = { + "com.yfd.platform.config", + "com.yfd.platform.datasource", + "com.yfd.platform.exception", + "com.yfd.platform.system", + "com.yfd.platform.utils", + "com.yfd.platform.component", + "com.yfd.business.css" + }, + exclude = {DataSourceAutoConfiguration.class, RedisAutoConfiguration.class} +) +@MapperScan(basePackages = { + "com.yfd.platform.**.mapper", + "com.yfd.business.css.**.mapper" +}) public class CriticalScenarioApplication { public static void main(String[] args) { SpringApplication.run(CriticalScenarioApplication.class, args); diff --git a/business-css/src/main/resources/application-business.yml b/business-css/src/main/resources/application-business.yml new file mode 100644 index 0000000..8facf84 --- /dev/null +++ b/business-css/src/main/resources/application-business.yml @@ -0,0 +1,21 @@ +server: + port: 8090 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + druid: + master: + driverClassName: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://43.138.168.68:3306/businessdb_css?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true + username: root + password: ylfw20230626@ + slave: + driverClassName: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://43.138.168.68:3306/businessdb_css?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true + username: root + password: ylfw20230626@ + +file-space: #项目文档空间 + files: D:\css\files\ #单独上传的文件附件 + system: D:\css\system\ #单独上传的文件 + diff --git a/business-css/src/main/resources/application.yml b/business-css/src/main/resources/application.yml index 724707e..75cce0b 100644 --- a/business-css/src/main/resources/application.yml +++ b/business-css/src/main/resources/application.yml @@ -1,21 +1,8 @@ spring: application: name: business-css - datasource: - url: jdbc:mysql://43.138.168.68:3306/frameworkdb2023?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true - username: root - password: ylfw20230626@ - driver-class-name: com.mysql.cj.jdbc.Driver - jackson: - time-zone: Asia/Shanghai - date-format: yyyy-MM-dd'T'HH:mm:ssXXX + profiles: + include: + - framework + - business -server: - port: 8082 - servlet: - context-path: / - -logging: - level: - root: info - com.yfd: debug \ No newline at end of file diff --git a/business-css/target/classes/application-business.yml b/business-css/target/classes/application-business.yml new file mode 100644 index 0000000..8facf84 --- /dev/null +++ b/business-css/target/classes/application-business.yml @@ -0,0 +1,21 @@ +server: + port: 8090 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + druid: + master: + driverClassName: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://43.138.168.68:3306/businessdb_css?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true + username: root + password: ylfw20230626@ + slave: + driverClassName: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://43.138.168.68:3306/businessdb_css?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true + username: root + password: ylfw20230626@ + +file-space: #项目文档空间 + files: D:\css\files\ #单独上传的文件附件 + system: D:\css\system\ #单独上传的文件 + diff --git a/business-css/target/classes/application.yml b/business-css/target/classes/application.yml index 724707e..75cce0b 100644 --- a/business-css/target/classes/application.yml +++ b/business-css/target/classes/application.yml @@ -1,21 +1,8 @@ spring: application: name: business-css - datasource: - url: jdbc:mysql://43.138.168.68:3306/frameworkdb2023?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true - username: root - password: ylfw20230626@ - driver-class-name: com.mysql.cj.jdbc.Driver - jackson: - time-zone: Asia/Shanghai - date-format: yyyy-MM-dd'T'HH:mm:ssXXX + profiles: + include: + - framework + - business -server: - port: 8082 - servlet: - context-path: / - -logging: - level: - root: info - com.yfd: debug \ No newline at end of file diff --git a/business-css/target/classes/com/yfd/business/css/CriticalScenarioApplication.class b/business-css/target/classes/com/yfd/business/css/CriticalScenarioApplication.class index 8923934..fcba66c 100644 Binary files a/business-css/target/classes/com/yfd/business/css/CriticalScenarioApplication.class and b/business-css/target/classes/com/yfd/business/css/CriticalScenarioApplication.class differ diff --git a/business-css/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/business-css/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index aeb255e..2c7b439 100644 --- a/business-css/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/business-css/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,3 +1,4 @@ +D:\JavaProjectSpace\business-css\src\main\java\com\yfd\business\css\controller\HealthController.java D:\JavaProjectSpace\business-css\src\main\java\com\yfd\business\css\controller\SimulationController.java D:\JavaProjectSpace\business-css\src\main\java\com\yfd\business\css\CriticalScenarioApplication.java D:\JavaProjectSpace\business-css\src\main\java\com\yfd\business\css\model\SimulationRequest.java diff --git a/framework/frontend/vite.config.ts b/framework/frontend/vite.config.ts index 12fe4f8..fe7de1f 100644 --- a/framework/frontend/vite.config.ts +++ b/framework/frontend/vite.config.ts @@ -27,7 +27,7 @@ export default ({ mode }: ConfigEnv): UserConfig => { // 线上API地址 //target: 'http://192.168.1.20:8090/', // 本地API地址 - target: 'http://localhost:8082', + target: 'http://localhost:8090', changeOrigin: true, rewrite: path => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '') diff --git a/framework/src/main/java/com/yfd/platform/config/SecurityConfig.java b/framework/src/main/java/com/yfd/platform/config/SecurityConfig.java index f1e1cfa..a4b2343 100644 --- a/framework/src/main/java/com/yfd/platform/config/SecurityConfig.java +++ b/framework/src/main/java/com/yfd/platform/config/SecurityConfig.java @@ -55,10 +55,9 @@ public class SecurityConfig { .requestMatchers("/user/code").permitAll() .requestMatchers(HttpMethod.GET, "/*.html", - "/**/*.html", - "/**/*.css", - "/**/*.js", - "/webSocket/**").permitAll() + "/webSocket/**", + "/assets/**", + "/icon/**").permitAll() .requestMatchers( "/swagger-ui.html", "/swagger-ui/**", @@ -74,7 +73,6 @@ public class SecurityConfig { "/avatar/**", "/systemurl/**", "/api/imageserver/upload").permitAll() - .requestMatchers("/**/**").permitAll() .anyRequest().authenticated() ) .cors(cors -> {}); diff --git a/framework/src/main/java/com/yfd/platform/config/WebConfig.java b/framework/src/main/java/com/yfd/platform/config/WebConfig.java index c242a34..45b051a 100644 --- a/framework/src/main/java/com/yfd/platform/config/WebConfig.java +++ b/framework/src/main/java/com/yfd/platform/config/WebConfig.java @@ -41,10 +41,13 @@ public class WebConfig implements WebMvcConfigurer { @SneakyThrows @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { - // 菜单图标访问路径 - String iconUrl = "file:" + System.getProperty("user.dir") + "\\src" + - "\\main\\resources\\static\\icon\\"; - registry.addResourceHandler("/menu/**").addResourceLocations(iconUrl).setCachePeriod(0); + registry.addResourceHandler("/icon/**") + .addResourceLocations("classpath:/static/icon/") + .setCachePeriod(0); + + registry.addResourceHandler("/assets/**") + .addResourceLocations("classpath:/static/assets/") + .setCachePeriod(0); registry.addResourceHandler("swagger-ui.html").addResourceLocations( "classpath:/META-INF/resources/"); diff --git a/framework/src/main/java/com/yfd/platform/datasource/DynamicDataSourceConfig.java b/framework/src/main/java/com/yfd/platform/datasource/DynamicDataSourceConfig.java index fc4a972..56002e3 100644 --- a/framework/src/main/java/com/yfd/platform/datasource/DynamicDataSourceConfig.java +++ b/framework/src/main/java/com/yfd/platform/datasource/DynamicDataSourceConfig.java @@ -26,11 +26,18 @@ public class DynamicDataSourceConfig { return DruidDataSourceBuilder.create().build(); } + @Bean + @ConfigurationProperties("spring.datasource.druid.slave") + public DataSource wglSlaveDataSource(){ + return DruidDataSourceBuilder.create().build(); + } + @Bean @Primary public DynamicDataSource dataSource(DataSource wglMasterDataSource, DataSource wglSlaveDataSource) { Map targetDataSources = new HashMap<>(); targetDataSources.put("master",wglMasterDataSource); + targetDataSources.put("slave",wglSlaveDataSource); return new DynamicDataSource(wglMasterDataSource, targetDataSources); } diff --git a/framework/src/main/resources/application-dev.yml b/framework/src/main/resources/application-dev.yml index 922f44e..4832cbf 100644 --- a/framework/src/main/resources/application-dev.yml +++ b/framework/src/main/resources/application-dev.yml @@ -1,5 +1,5 @@ server: - port: 8093 + port: 8090 spring: #应用名称 @@ -10,12 +10,12 @@ spring: druid: master: driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://43.138.168.68:3306/frameworkdb2023?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true + url: jdbc:mysql://43.138.168.68:3306/businessdb_css?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true username: root password: ylfw20230626@ slave: driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://43.138.168.68:3306/frameworkdb2023?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true + url: jdbc:mysql://43.138.168.68:3306/businessdb_css?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true username: root password: ylfw20230626@ diff --git a/framework/src/main/resources/application-framework.yml b/framework/src/main/resources/application-framework.yml new file mode 100644 index 0000000..5e3c670 --- /dev/null +++ b/framework/src/main/resources/application-framework.yml @@ -0,0 +1,30 @@ +jasypt: + encryptor: + password: salt + +# 密码加密传输,前端公钥加密,后端私钥解密(共性配置) +rsa: + private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A== + +# Actuator & Micrometer 默认配置(共性) +management: + endpoints: + web: + exposure: + include: health,info,metrics,prometheus,env,beans,threaddump,loggers,configprops + endpoint: + health: + show-details: always + metrics: + tags: + application: ${spring.application.name:platform} + + +# Springdoc 默认配置(共性) +springdoc: + api-docs: + enabled: true + swagger-ui: + enabled: true + path: /swagger-ui.html + packages-to-scan: com.yfd.platform \ No newline at end of file diff --git a/framework/src/main/resources/application-server.yml b/framework/src/main/resources/application-server.yml index 22018e7..9d9e3e1 100644 --- a/framework/src/main/resources/application-server.yml +++ b/framework/src/main/resources/application-server.yml @@ -37,7 +37,6 @@ swagger-ui: file-space: #项目文档空间 files: D:\demoproject\files\ #单独上传的文件附件 - 3dmodel: D:\demoproject\3dmodel\ #单独上传的文件附件 useravatar: D:\demoproject\useravatar\ #用户头像 system: D:\demoproject\system\ #系统文档根目录,用于头像等静态资源 diff --git a/framework/src/main/resources/application.yml b/framework/src/main/resources/application.yml index 85d5749..7d97f80 100644 --- a/framework/src/main/resources/application.yml +++ b/framework/src/main/resources/application.yml @@ -22,9 +22,7 @@ management: metrics: tags: application: ${spring.application.name:platform} - export: - prometheus: - enabled: true + # Springdoc 默认配置 springdoc: diff --git a/系统代码结构框架规划.md b/系统代码结构框架规划.md new file mode 100644 index 0000000..337139e --- /dev/null +++ b/系统代码结构框架规划.md @@ -0,0 +1,221 @@ +# 系统代码结构框架规划 + +## 1. 目标与原则 + +### 1.1 业务目标 +- framework 模块:沉淀系统级通用能力(安全、认证、权限、日志、任务调度、文件、缓存、监控、通用工具等),**不依赖任何业务代码**,可被 N 个业务模块复用 +- business-css 模块:仅聚焦“临界事故情景分析”业务,**零系统代码**,所有共性能力通过 framework 提供 + +### 1.2 设计原则 +| 原则 | 落地要求 | +|----|----| +| **高内聚低耦合** | 业务模块只依赖 framework,framework 绝不反向依赖业务 | +| **DRY** | 同一能力只在一个地方实现(framework),禁止业务模块重复造轮子 | +| **可演进** | 后续新增业务模块(business-xxx)零改动即可接入现有能力 | +| **可维护** | 通过 Maven 多模块 + 分层分包,让“修改点”收敛到最小范围 | + +--- + +## 2. 多模块 Maven 结构(聚合工程) + +``` +d:\JavaProjectSpace (root pom, packaging=pom) +├── framework (com.yfd:platform:1.0) +│ ├── src/main/java +│ ├── src/main/resources +│ └── pom.xml +├── business-css (com.yfd:business-css:1.0-SNAPSHOT) +│ ├── src/main/java +│ ├── src/main/resources +│ └── pom.xml +└── pom.xml ← modules 声明 +``` + +--- + +## 3. framework 模块职责与目录 + +### 3.1 职责矩阵 +| 能力域 | 是否包含 | 说明 | +|----|----|----| +| 统一配置 | ✅ | 数据源、Redis、Jackson、MyBatis-Plus、Swagger、监控、日志、加解密等 | +| 安全 & 认证 | ✅ | Spring Security + JWT 过滤器、权限注解、匿名访问标记 | +| 系统级 API | ✅ | 用户/角色/菜单/部门/字典/日志/文件/消息/定时任务等 | +| 通用工具 | ✅ | RSA、Excel、IP 归属、验证码、异步线程池、缓存封装 | +| 前端静态 | ✅ | 仅提供全局通用静态(如 404、error、swagger-ui);**业务页面由业务模块提供** | +| 业务代码 | ❌ | 禁止出现任何与“临界事故”相关的类、表、API | + +### 3.2 代码分层 +``` +src/main/java/com/yfd/platform +├── config # 自动配置类(DataSource、Security、Async、Redis、MyBatisPlus...) +│ ├── bean # @ConfigurationProperties 配置属性类 +│ ├── thread # 线程池配置 +│ └── ... +├── constant # 系统级常量 +├── exception # 全局异常 + 自定义业务异常基类 +├── annotation # 系统注解(@Log、@AnonymousAccess 等) +├── aspect # 系统级 AOP(操作日志、权限校验) +├── datasource # 动态数据源切换(若业务需要多库) +├── utils # 通用工具(RSA、Excel、IP、验证码、SpringContextHolder...) +├── system # 系统管理域(用户/角色/菜单/部门/字典/日志/文件/消息/任务) +│ ├── domain # PO / DTO / VO(注意:仅系统表) +│ ├── mapper # MyBatis-Plus Mapper +│ ├── service # Service 接口 + 实现 +│ └── controller # REST API(/api/system/*) +└── component # 非业务组件(WebSocket、SSE、任务执行器) +``` + +### 3.3 资源目录 +``` +src/main/resources +├── mapper/system/*.xml # 仅系统表 SQL 映射 +├── static # 全局通用静态(swagger、error、login 背景图等) +├── banner.txt / logback.xml # 统一启动 Banner & 日志 +└── application*.yml # 框架级默认配置(数据源、redis、监控、线程池...) +``` + +> **注意**:framework 绝不放业务 SQL、业务静态页面;业务资源全部下沉到业务模块。 + +--- + +## 4. business-css 模块职责与目录 + +### 4.1 职责矩阵 +| 能力域 | 是否包含 | 说明 | +|----|----|----| +| 业务领域代码 | ✅ | 临界事故实体、服务、控制器、DTO、Mapper XML | +| 业务静态页面 | ✅ | 仅与“临界事故”相关的页面、图片、图标 | +| 系统能力 | ❌ | 通过依赖 framework 获得,禁止复制粘贴 | +| 系统表 SQL | ❌ | 系统表由 framework 提供,业务模块只写业务表 | + +### 4.2 代码分层 +``` +src/main/java/com/yfd/business/css +├── model # 业务实体 / DTO / VO(仅业务表) +├── mapper # MyBatis-Plus Mapper 接口 +├── service # 业务服务接口 + 实现 +├── controller # REST API(/api/css/* 或 /api/simulation/*) +└── *Application.java +``` + +### 4.3 资源目录 +``` +src/main/resources +├── mapper/css/*.xml # 仅业务表 SQL 映射 +├── static/css/* # 业务静态(页面、图片、图标) +└── application.yml # 业务个性化配置(端口、数据源 URL、业务开关) +``` + +--- + +## 5. 依赖关系与 Maven 坐标 + +| 模块 | 坐标 | 依赖 | +|----|----|----| +| framework | com.yfd:platform:1.0 | 无业务依赖,仅技术栈(Spring Boot、MyBatis-Plus、Security、Redis...) | +| business-css | com.yfd:business-css:1.0-SNAPSHOT | com.yfd:platform:1.0(plain classifier) | + +> **plain classifier**:framework 通过 `maven-jar-plugin` 额外生成 `platform-1.0-plain.jar`,仅含 classes & resources,不含 Spring-Boot 启动器,避免业务模块引入重复主类。 + +--- + +## 6. 配置隔离策略 + +| 配置项 | 放在哪里 | 示例 | +|----|----|----| +| 数据源、Redis、MyBatis、线程池、监控、日志 | framework | `framework/src/main/resources/application.yml` | +| 业务开关、业务参数、端口、上下文 | business-css | `business-css/src/main/resources/application.yml` | +| 环境差异化(dev/prod) | 各自模块 | `application-dev.yml`、`application-prod.yml` | + +> **启动顺序**:业务模块的 `application.yml` 会覆盖 framework 的同名 key,实现“共性 + 个性”组合。 + +--- + +## 7. 静态资源隔离策略 + +| 资源类型 | 放在哪里 | 访问路径 | 说明 | +|----|----|----|----| +| 全局通用(swagger、error、login 背景) | framework | `/error`、`/swagger-ui.html` | 所有业务模块共用 | +| 业务页面(*.html、*.js、*.png) | business-css | `/css/index.html` | 仅当前业务使用 | +| 业务接口 | business-css | `/api/simulation/*` | REST 无静态冲突 | + +> **最佳实践**:业务静态统一放在 `/static/{业务简称}/` 目录,避免文件名冲突。 + +--- + +## 8. 数据库隔离策略 + +| 表归属 | 模块 | 说明 | +|----|----|----| +| 系统表(sys_user、sys_role、sys_menu...) | framework | 由 framework 提供 Mapper & XML,业务模块只读/调用 Service | +| 业务表(css_simulation、css_record...) | business-css | 由业务模块自行创建 Mapper & XML,禁止写入系统表 SQL | + +> **多业务共享同一库**:系统表前缀 `sys_`,业务表前缀 `css_`、`xxx_`,物理共存但逻辑隔离。 + +--- + +## 9. 启动与部署 + +### 9.1 开发阶段 +```bash +# 1. 安装 framework 到本地仓库 +mvn -DskipTests clean install -pl framework + +# 2. 启动业务模块(自动依赖 framework) +mvn -DskipTests spring-boot:run -pl business-css +``` + +### 9.2 交付阶段 +```bash +# 一次性打包所有模块 +mvn -DskipTests clean package + +# 产物 +framework/target/platform-1.0.jar # 系统后台服务(可选独立部署) +business-css/target/business-css-1.0-SNAPSHOT.jar # 业务服务(含内嵌 Tomcat) +``` + +> **部署方式**: +> - 轻量场景:只部署 `business-css-1.0-SNAPSHOT.jar`,内嵌 framework 能力 +> - 分离场景:独立部署 `platform-1.0.jar`(提供统一后台),再部署多个业务 jar,网关统一路由 + +--- + +## 10. 横向扩展:新增业务模块(business-xxx) + +1. 新建 Maven 模块 `business-xxx`,parent 指向根 pom +2. 依赖 `com.yfd:platform:1.0:plain` +3. 包名 `com.yfd.business.xxx` +4. 表前缀 `xxx_` +5. 静态资源 `/static/xxx/` +6. API 前缀 `/api/xxx/` +7. 端口 `8083`(避免与 css 冲突) + +**零改动接入**:无需复制任何系统代码,仅需以上 7 步即可拥有完整的用户/权限/日志/文件/任务能力。 + +--- + +## 11. 常见误区与禁止项 + +| 误区 | 正确做法 | +|----|----| +| 把系统表 SQL 写在业务模块 | 系统 SQL 只能放在 framework/mapper/system | +| 把系统静态页面(login、404)复制到业务模块 | 全局静态统一收敛到 framework/static | +| 业务模块直接依赖 `platform-1.0.jar`(可执行) | 使用 `platform-1.0-plain.jar`(仅 classes) | +| framework 里出现“css”字样 | framework 必须业务无关,出现业务代码即重构 | +| 多个业务模块端口重复 | 每个业务模块独立端口(8082、8083...) | + +--- + +## 12. 演进路线 + +| 阶段 | 动作 | +|----|----| +| **当前** | 单库多模块,Maven 聚合,静态/配置/表逻辑隔离 | +| **下一步** | 引入 Spring Cloud Gateway,统一路由 & 鉴权,业务模块只关注领域逻辑 | +| **未来** | 业务模块拆分为独立微服务(docker-compose/k8s),framework 作为共享 lib 或 sidecar | + +--- + +> **一句话总结**:framework 做“平台”,business-css 做“产品”;平台沉淀,产品迭代,互不污染,横向复制。 \ No newline at end of file