ember_xc 1 сар өмнө
commit
07b16fd633
100 өөрчлөгдсөн 2698 нэмэгдсэн , 0 устгасан
  1. 5 0
      .idea/.gitignore
  2. 21 0
      .idea/claudeCodeEditorTabs.xml
  3. 25 0
      .idea/compiler.xml
  4. 9 0
      .idea/encodings.xml
  5. 20 0
      .idea/jarRepositories.xml
  6. 15 0
      .idea/misc.xml
  7. 9 0
      .idea/position-system.iml
  8. 3 0
      .mvn/maven.config
  9. 4 0
      .vscode/settings.json
  10. 138 0
      CLAUDE.md
  11. 121 0
      pom.xml
  12. 37 0
      position-common/pom.xml
  13. 53 0
      position-common/src/main/java/com/example/position/common/api/Result.java
  14. 20 0
      position-common/src/main/java/com/example/position/common/config/CorsConfig.java
  15. 26 0
      position-common/src/main/java/com/example/position/common/config/SaTokenAuthConfig.java
  16. 37 0
      position-common/src/main/java/com/example/position/common/enums/ResultCode.java
  17. 27 0
      position-common/src/main/java/com/example/position/common/exception/BusinessException.java
  18. 96 0
      position-common/src/main/java/com/example/position/common/exception/GlobalExceptionHandler.java
  19. BIN
      position-common/target/classes/com/example/position/common/api/Result.class
  20. BIN
      position-common/target/classes/com/example/position/common/config/CorsConfig.class
  21. BIN
      position-common/target/classes/com/example/position/common/config/SaTokenAuthConfig.class
  22. BIN
      position-common/target/classes/com/example/position/common/enums/ResultCode.class
  23. BIN
      position-common/target/classes/com/example/position/common/exception/BusinessException.class
  24. BIN
      position-common/target/classes/com/example/position/common/exception/GlobalExceptionHandler.class
  25. 6 0
      position-common/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  26. 6 0
      position-common/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  27. 103 0
      position-service/pom.xml
  28. 13 0
      position-service/src/main/java/com/example/position/Application.java
  29. 27 0
      position-service/src/main/java/com/example/position/controller/HealthController.java
  30. 71 0
      position-service/src/main/java/com/example/position/controller/PositionController.java
  31. 67 0
      position-service/src/main/java/com/example/position/domain/entity/Position.java
  32. 24 0
      position-service/src/main/java/com/example/position/domain/repository/PositionRepository.java
  33. 30 0
      position-service/src/main/java/com/example/position/infrastructure/config/DruidConfig.java
  34. 20 0
      position-service/src/main/java/com/example/position/infrastructure/config/MyBatisPlusConfig.java
  35. 23 0
      position-service/src/main/java/com/example/position/infrastructure/config/OpenApiConfig.java
  36. 12 0
      position-service/src/main/java/com/example/position/infrastructure/mapper/PositionMapper.java
  37. 95 0
      position-service/src/main/java/com/example/position/infrastructure/repository/PositionRepositoryImpl.java
  38. 26 0
      position-service/src/main/java/com/example/position/service/PositionApplicationService.java
  39. 46 0
      position-service/src/main/java/com/example/position/service/converter/PositionConverter.java
  40. 32 0
      position-service/src/main/java/com/example/position/service/dto/PositionCreateDTO.java
  41. 29 0
      position-service/src/main/java/com/example/position/service/dto/PositionQueryDTO.java
  42. 28 0
      position-service/src/main/java/com/example/position/service/dto/PositionResponseDTO.java
  43. 32 0
      position-service/src/main/java/com/example/position/service/dto/PositionUpdateDTO.java
  44. 113 0
      position-service/src/main/java/com/example/position/service/impl/PositionApplicationServiceImpl.java
  45. 16 0
      position-service/src/main/resources/application-local.yml
  46. 74 0
      position-service/src/main/resources/application.yml
  47. 36 0
      position-service/src/main/resources/mapper/PositionMapper.xml
  48. 50 0
      position-service/src/main/resources/sql/schema-oracle.sql
  49. 272 0
      position-service/src/test/java/com/example/position/controller/PositionControllerTest.java
  50. 264 0
      position-service/src/test/java/com/example/position/service/impl/PositionApplicationServiceImplTest.java
  51. 16 0
      position-service/target/classes/application-local.yml
  52. 74 0
      position-service/target/classes/application.yml
  53. BIN
      position-service/target/classes/com/example/position/Application.class
  54. BIN
      position-service/target/classes/com/example/position/controller/HealthController.class
  55. BIN
      position-service/target/classes/com/example/position/controller/PositionController.class
  56. BIN
      position-service/target/classes/com/example/position/domain/entity/Position.class
  57. BIN
      position-service/target/classes/com/example/position/domain/repository/PositionRepository.class
  58. BIN
      position-service/target/classes/com/example/position/infrastructure/config/DruidConfig.class
  59. BIN
      position-service/target/classes/com/example/position/infrastructure/config/MyBatisPlusConfig.class
  60. BIN
      position-service/target/classes/com/example/position/infrastructure/config/OpenApiConfig.class
  61. BIN
      position-service/target/classes/com/example/position/infrastructure/mapper/PositionMapper.class
  62. BIN
      position-service/target/classes/com/example/position/infrastructure/repository/PositionRepositoryImpl.class
  63. BIN
      position-service/target/classes/com/example/position/service/PositionApplicationService.class
  64. BIN
      position-service/target/classes/com/example/position/service/converter/PositionConverter.class
  65. BIN
      position-service/target/classes/com/example/position/service/converter/PositionConverterImpl.class
  66. BIN
      position-service/target/classes/com/example/position/service/dto/PositionCreateDTO.class
  67. BIN
      position-service/target/classes/com/example/position/service/dto/PositionQueryDTO.class
  68. BIN
      position-service/target/classes/com/example/position/service/dto/PositionResponseDTO.class
  69. BIN
      position-service/target/classes/com/example/position/service/dto/PositionUpdateDTO.class
  70. BIN
      position-service/target/classes/com/example/position/service/impl/PositionApplicationServiceImpl.class
  71. 36 0
      position-service/target/classes/mapper/PositionMapper.xml
  72. 50 0
      position-service/target/classes/sql/schema-oracle.sql
  73. 119 0
      position-service/target/generated-sources/annotations/com/example/position/service/converter/PositionConverterImpl.java
  74. 18 0
      position-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  75. 17 0
      position-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  76. 10 0
      position-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
  77. 2 0
      position-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
  78. 6 0
      position-service/target/surefire-reports/TEST-com.example.position.controller.PositionControllerTest.xml
  79. 6 0
      position-service/target/surefire-reports/TEST-com.example.position.service.impl.PositionApplicationServiceImplTest.xml
  80. 4 0
      position-service/target/surefire-reports/com.example.position.controller.PositionControllerTest.txt
  81. 4 0
      position-service/target/surefire-reports/com.example.position.service.impl.PositionApplicationServiceImplTest.txt
  82. BIN
      position-service/target/test-classes/com/example/position/controller/PositionControllerTest$Create.class
  83. BIN
      position-service/target/test-classes/com/example/position/controller/PositionControllerTest$Delete.class
  84. BIN
      position-service/target/test-classes/com/example/position/controller/PositionControllerTest$Query.class
  85. BIN
      position-service/target/test-classes/com/example/position/controller/PositionControllerTest$Update.class
  86. BIN
      position-service/target/test-classes/com/example/position/controller/PositionControllerTest.class
  87. BIN
      position-service/target/test-classes/com/example/position/service/impl/PositionApplicationServiceImplTest$CreatePosition.class
  88. BIN
      position-service/target/test-classes/com/example/position/service/impl/PositionApplicationServiceImplTest$DeletePosition.class
  89. BIN
      position-service/target/test-classes/com/example/position/service/impl/PositionApplicationServiceImplTest$QueryPosition.class
  90. BIN
      position-service/target/test-classes/com/example/position/service/impl/PositionApplicationServiceImplTest$UpdatePosition.class
  91. BIN
      position-service/target/test-classes/com/example/position/service/impl/PositionApplicationServiceImplTest.class
  92. 12 0
      position-ui/index.html
  93. 12 0
      position-ui/node_modules/.bin/acorn
  94. 17 0
      position-ui/node_modules/.bin/acorn.cmd
  95. 28 0
      position-ui/node_modules/.bin/acorn.ps1
  96. 12 0
      position-ui/node_modules/.bin/esbuild
  97. 17 0
      position-ui/node_modules/.bin/esbuild.cmd
  98. 28 0
      position-ui/node_modules/.bin/esbuild.ps1
  99. 12 0
      position-ui/node_modules/.bin/nanoid
  100. 17 0
      position-ui/node_modules/.bin/nanoid.cmd

+ 5 - 0
.idea/.gitignore

@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/

+ 21 - 0
.idea/claudeCodeEditorTabs.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ClaudeCodeEditorTabs">
+    <option name="activeSessionId" value="c2a20cfc-dc21-442e-b7e9-ae3d2b7ae7e1" />
+    <option name="openSessionIds">
+      <list>
+        <option value="c2a20cfc-dc21-442e-b7e9-ae3d2b7ae7e1" />
+      </list>
+    </option>
+    <option name="sessionPaths">
+      <map>
+        <entry key="c2a20cfc-dc21-442e-b7e9-ae3d2b7ae7e1" value="/sessions/15751376-5dee-49ba-a75b-7b299b83ea51" />
+      </map>
+    </option>
+    <option name="sessionTitles">
+      <map>
+        <entry key="c2a20cfc-dc21-442e-b7e9-ae3d2b7ae7e1" value="你好" />
+      </map>
+    </option>
+  </component>
+</project>

+ 25 - 0
.idea/compiler.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <annotationProcessing>
+      <profile default="true" name="Default" enabled="true" />
+      <profile name="Annotation profile for position-management-system" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <processorPath useClasspath="false">
+          <entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.30/lombok-1.18.30.jar" />
+          <entry name="$MAVEN_REPOSITORY$/org/mapstruct/mapstruct-processor/1.5.5.Final/mapstruct-processor-1.5.5.Final.jar" />
+        </processorPath>
+        <module name="position-common" />
+        <module name="position-service" />
+      </profile>
+    </annotationProcessing>
+  </component>
+  <component name="JavacSettings">
+    <option name="ADDITIONAL_OPTIONS_OVERRIDE">
+      <module name="position-common" options="-parameters" />
+      <module name="position-service" options="-parameters" />
+    </option>
+  </component>
+</project>

+ 9 - 0
.idea/encodings.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/position-common/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/position-service/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
+  </component>
+</project>

+ 20 - 0
.idea/jarRepositories.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Central Repository" />
+      <option name="url" value="https://maven.aliyun.com/repository/public" />
+    </remote-repository>
+  </component>
+</project>

+ 15 - 0
.idea/misc.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="KubernetesApiProvider">{}</component>
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" project-jdk-name="ms-17" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+</project>

+ 9 - 0
.idea/position-system.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 3 - 0
.mvn/maven.config

@@ -0,0 +1,3 @@
+# 使用默认本地仓库路径 ~/.m2/repository(取消自定义 D:\A-work\positionRes)
+# 如需切换为其他路径,取消下方注释并修改
+# -Dmaven.repo.local=D:\A-work\positionRes

+ 4 - 0
.vscode/settings.json

@@ -0,0 +1,4 @@
+{
+    "java.configuration.updateBuildConfiguration": "interactive",
+    "java.compile.nullAnalysis.mode": "automatic"
+}

+ 138 - 0
CLAUDE.md

@@ -0,0 +1,138 @@
+# CLAUDE.md – 头寸管理系统(Position Management System)
+
+## 项目概述
+本项目实现头寸管理功能从现有资管系统(XAMS)中的完全解耦,构建独立的头寸管理系统。系统提供头寸的生成、更新、删除、查询及规则驱动的自动化处理能力,通过标准 REST API 供上游系统调用。
+
+**核心目标**:高内聚、低耦合、可扩展、可维护,支持平滑迁移。
+
+---
+
+## 技术栈(强制)
+
+| 类别          | 技术选型                | 版本/说明                                    |
+| ------------- | ----------------------- | -------------------------------------------- |
+| 语言          | Java                    | 17                                           |
+| 框架          | Spring Boot             | 2.7.18                                       |
+| 微服务        | Spring Cloud            | 2021.0.8                                     |
+| 构建工具      | Maven                   | 3.8+,多模块结构                             |
+| 服务注册/配置 | Nacos                   | 2021.0.5.0(本地可禁用)                     |
+| API网关       | Spring Cloud Gateway    | 3.x(生产环境)                              |
+| ORM           | MyBatis-Plus            | 3.5.5(含分页插件、乐观锁插件)              |
+| **数据库**    | **Oracle**              | **19c(生产)/ XE(开发)**                  |
+| 连接池        | Druid                   | 1.2.20,自带 SQL 监控页面 `/druid`           |
+| 缓存          | Redis 6.x(Lettuce)+ Caffeine 3.1.8(本地缓存) | 本地可禁用 Redis                |
+| 消息队列      | RocketMQ                | 4.9.x(spring-boot-starter 2.2.3)           |
+| 认证授权      | Sa-Token                | 1.37.0,无状态 + 拦截器,本地可关闭          |
+| 接口文档      | SpringDoc OpenAPI 3     | 1.7.0,`/swagger-ui.html`                    |
+| 对象映射      | MapStruct               | 1.5.5.Final,编译期生成                       |
+| 监控          | Spring Boot Actuator + Micrometer Prometheus | `/actuator/health`、`/actuator/prometheus` |
+| 日志          | Logback + SLF4J         | 生产配合 ELK 输出 JSON 格式                  |
+| 参数校验      | JSR-303(spring-boot-starter-validation) | `@Valid` + `@Validated`         |
+
+**本地开发**:所有依赖从阿里云 Maven 镜像(`https://maven.aliyun.com/repository/public`)下载,本地仓库路径 `D:\maven-repository`(见 `~/.m2/settings.xml`),不依赖内网 Nexus。
+
+---
+
+## 项目模块结构(Maven Multi-Module)
+
+```text
+position-management-system/
+├── pom.xml                      # 父POM,统一依赖版本管理
+├── position-common/             # 公共组件:Result/ResultCode、异常体系、CORS、Sa-Token配置
+├── position-domain/             # 领域模型:实体、值对象、仓储接口、领域服务接口、领域事件
+├── position-infrastructure/     # 基础设施:持久化实现、MyBatis-Plus配置、Druid配置、缓存实现
+├── position-application/        # 应用层:应用服务实现(编排领域服务)、命令/查询对象、DTO
+├── position-api/                # 接口层:REST Controller、OpenAPI配置、多环境配置
+├── position-scheduler/          # 定时任务:基于 Spring Scheduled,执行批处理、数据核对、缓存刷新
+└── position-admin/              # (可选)管理后台:提供简单的配置界面,初期可省略
+```
+
+---
+
+## 框架规范(强制遵守)
+
+### 1. 统一返回结构
+所有 Controller 接口必须返回 `Result<T>`(位于 `position-common`):
+```java
+Result.success(data);                    // 成功带数据
+Result.success();                        // 成功无数据
+Result.fail(ResultCode.DATA_NOT_FOUND);  // 失败指定码
+```
+响应 JSON 结构:
+```json
+{ "code": 200, "message": "操作成功", "data": {}, "success": true }
+```
+
+### 2. 异常处理
+- 业务异常抛 `BusinessException(ResultCode, message)`,由 `GlobalExceptionHandler` 统一捕获
+- 参数校验异常(`@Valid`、`@Validated`)自动转为 `Result.fail(PARAM_INVALID, ...)`
+- Sa-Token 未登录/无权限异常自动转为 401/403 Result
+- 禁止在 Controller 中 try-catch 后自行返回
+
+### 3. 多环境配置(Spring Profile)
+配置文件位于 `position-api/src/main/resources/`:
+- `application.yml` — 公共配置(端口、Druid 连接池、MyBatis-Plus、Sa-Token、SpringDoc、Actuator)
+- `application-local.yml` — 本地开发:禁用 Nacos/Redis/RocketMQ,关闭认证拦截
+- `application-dev.yml` — 开发环境:连 Nacos/Redis/RocketMQ,开启认证拦截
+- `application-prod.yml` — 生产环境:连接生产中间件,开启认证与监控
+
+启动切换:
+```bash
+# 本地(默认)
+mvn -pl position-api spring-boot:run
+# 指定环境
+java -jar position-api.jar --spring.profiles.active=dev
+```
+
+### 4. 数据库访问
+- 实体继承 MyBatis-Plus 体系,使用 `@TableName`、`@TableId`、`@Version`、`@TableLogic` 注解
+- Mapper 继承 `BaseMapper<T>`,复杂查询写 XML(`position-infrastructure/src/main/resources/mapper/`)
+- 分页统一使用 `Page<T>`(MyBatis-Plus 分页插件已配置,Oracle 方言)
+- 逻辑删除字段:`deleted`(0 未删除,1 已删除,已在全局配置)
+
+### 5. 认证授权(Sa-Token)
+- 本地开发:`sa-token.enable-auth=false`,所有接口放行
+- dev/prod:`sa-token.enable-auth=true`,`/api/**` 需登录
+- 放行路径:`/api/health`、`/api/auth/login`、接口文档、Druid 监控
+- Token 通过 `Authorization` 请求头传递
+
+### 6. 接口路径规范
+- 所有业务接口统一前缀 `/api/`,如 `/api/positions`、`/api/positions/{id}`
+- 健康检查:`GET /api/health`
+- 接口文档:`/swagger-ui.html`(本地与 dev 环境)
+
+### 7. 监控端点
+- 健康检查:`GET /actuator/health`
+- Prometheus 指标:`GET /actuator/prometheus`
+- Druid SQL 监控:`/druid/index.html`(账号 admin/admin)
+
+---
+
+## 本地运行
+
+### 前置条件
+- JDK 17(当前配置 `D:\JDK`)
+- Oracle 19c/XE,监听 `localhost:1521`,SID `ORCL`,账号 `system/Root123456`
+- Maven 本地仓库:`D:\maven-repository`(已在 `~/.m2/settings.xml` 配置)
+
+### 构建与启动
+```powershell
+$env:JAVA_HOME = "D:\JDK"
+$env:Path = "$env:JAVA_HOME\bin;$env:Path"
+mvn clean install -DskipTests -B
+mvn -pl position-api spring-boot:run
+```
+
+### 验证
+- 健康检查:`GET http://localhost:8080/api/health`
+- 接口文档:`http://localhost:8080/swagger-ui.html`
+- Druid 监控:`http://localhost:8080/druid/index.html`
+- Actuator:`http://localhost:8080/actuator/health`
+
+---
+
+## 编码约定
+- 实体类使用 Lombok `@Data`;DTO ↔ Entity 转换使用 MapStruct
+- 包结构:`com.example.position.{模块}.{分层}`
+- 日志使用 SLF4J(`@Slf4j`),关键业务操作记录 INFO,异常记录 ERROR
+- 所有对外接口使用 `@Tag`、`@Operation` 注解生成文档

+ 121 - 0
pom.xml

@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.18</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>com.example</groupId>
+    <artifactId>position-management-system</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <name>position-management-system</name>
+    <description>Position Management System - 微服务架构</description>
+
+    <modules>
+        <module>position-common</module>
+        <module>position-service</module>
+    </modules>
+
+    <properties>
+        <java.version>17</java.version>
+        <mybatis-plus.version>3.5.5</mybatis-plus.version>
+        <oracle-jdbc.version>19.18.0.0</oracle-jdbc.version>
+        <lombok.version>1.18.30</lombok.version>
+        <springdoc.version>1.7.0</springdoc.version>
+        <druid.version>1.2.20</druid.version>
+        <mapstruct.version>1.5.5.Final</mapstruct.version>
+        <sa-token.version>1.37.0</sa-token.version>
+        <caffeine.version>3.1.8</caffeine.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.example</groupId>
+                <artifactId>position-common</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.oracle.database.jdbc</groupId>
+                <artifactId>ojdbc8</artifactId>
+                <version>${oracle-jdbc.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.oracle.database.nls</groupId>
+                <artifactId>orai18n</artifactId>
+                <version>${oracle-jdbc.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>druid-spring-boot-starter</artifactId>
+                <version>${druid.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-ui</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.mapstruct</groupId>
+                <artifactId>mapstruct</artifactId>
+                <version>${mapstruct.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.mapstruct</groupId>
+                <artifactId>mapstruct-processor</artifactId>
+                <version>${mapstruct.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-spring-boot-starter</artifactId>
+                <version>${sa-token.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.github.ben-manes.caffeine</groupId>
+                <artifactId>caffeine</artifactId>
+                <version>${caffeine.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <configuration>
+                        <source>${java.version}</source>
+                        <target>${java.version}</target>
+                        <encoding>${project.build.sourceEncoding}</encoding>
+                        <annotationProcessorPaths>
+                            <path>
+                                <groupId>org.projectlombok</groupId>
+                                <artifactId>lombok</artifactId>
+                                <version>${lombok.version}</version>
+                            </path>
+                            <path>
+                                <groupId>org.mapstruct</groupId>
+                                <artifactId>mapstruct-processor</artifactId>
+                                <version>${mapstruct.version}</version>
+                            </path>
+                        </annotationProcessorPaths>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+</project>

+ 37 - 0
position-common/pom.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.example</groupId>
+        <artifactId>position-management-system</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>position-common</artifactId>
+    <packaging>jar</packaging>
+    <name>position-common</name>
+    <description>公共组件 - Result/ResultCode、异常体系、CORS、Sa-Token配置</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot-starter</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 53 - 0
position-common/src/main/java/com/example/position/common/api/Result.java

@@ -0,0 +1,53 @@
+package com.example.position.common.api;
+
+import com.example.position.common.enums.ResultCode;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class Result<T> implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private int code;
+    private String message;
+    private T data;
+
+    private Result() {
+    }
+
+    private Result(int code, String message, T data) {
+        this.code = code;
+        this.message = message;
+        this.data = data;
+    }
+
+    public static <T> Result<T> success() {
+        return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null);
+    }
+
+    public static <T> Result<T> success(T data) {
+        return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
+    }
+
+    public static <T> Result<T> success(String message, T data) {
+        return new Result<>(ResultCode.SUCCESS.getCode(), message, data);
+    }
+
+    public static <T> Result<T> fail(ResultCode resultCode) {
+        return new Result<>(resultCode.getCode(), resultCode.getMessage(), null);
+    }
+
+    public static <T> Result<T> fail(ResultCode resultCode, String message) {
+        return new Result<>(resultCode.getCode(), message, null);
+    }
+
+    public static <T> Result<T> fail(int code, String message) {
+        return new Result<>(code, message, null);
+    }
+
+    public boolean isSuccess() {
+        return this.code == ResultCode.SUCCESS.getCode();
+    }
+}

+ 20 - 0
position-common/src/main/java/com/example/position/common/config/CorsConfig.java

@@ -0,0 +1,20 @@
+package com.example.position.common.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CorsConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedOriginPatterns("*")
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")
+                .allowedHeaders("*")
+                .exposedHeaders("Authorization", "Content-Disposition")
+                .allowCredentials(true)
+                .maxAge(3600);
+    }
+}

+ 26 - 0
position-common/src/main/java/com/example/position/common/config/SaTokenAuthConfig.java

@@ -0,0 +1,26 @@
+package com.example.position.common.config;
+
+import cn.dev33.satoken.interceptor.SaInterceptor;
+import cn.dev33.satoken.stp.StpUtil;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+@ConditionalOnProperty(name = "sa-token.enable-auth", havingValue = "true")
+public class SaTokenAuthConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
+                .addPathPatterns("/api/**")
+                .excludePathPatterns(
+                        "/api/health",
+                        "/api/auth/login",
+                        "/v3/api-docs/**",
+                        "/swagger-ui/**",
+                        "/swagger-ui.html"
+                );
+    }
+}

+ 37 - 0
position-common/src/main/java/com/example/position/common/enums/ResultCode.java

@@ -0,0 +1,37 @@
+package com.example.position.common.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum ResultCode {
+
+    SUCCESS(200, "操作成功"),
+
+    FAILURE(500, "操作失败"),
+
+    PARAM_INVALID(400, "参数校验失败"),
+    PARAM_MISSING(40001, "缺少必要参数"),
+
+    BUSINESS_ERROR(40002, "业务处理失败"),
+    DATA_NOT_FOUND(40003, "数据不存在"),
+    DATA_DUPLICATED(40004, "数据已存在"),
+    DATA_INCONSISTENT(40005, "数据不一致,可能已被修改"),
+
+    UNAUTHORIZED(401, "未登录或登录已过期"),
+    FORBIDDEN(403, "无权限访问"),
+    TOKEN_INVALID(40101, "Token 无效"),
+    TOKEN_EXPIRED(40102, "Token 已过期"),
+
+    SYSTEM_ERROR(500, "系统异常"),
+    SERVICE_UNAVAILABLE(503, "服务暂不可用"),
+    REMOTE_CALL_ERROR(50001, "远程调用失败"),
+    DB_ERROR(50002, "数据库操作异常");
+
+    private final int code;
+    private final String message;
+
+    ResultCode(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+}

+ 27 - 0
position-common/src/main/java/com/example/position/common/exception/BusinessException.java

@@ -0,0 +1,27 @@
+package com.example.position.common.exception;
+
+import com.example.position.common.enums.ResultCode;
+import lombok.Getter;
+
+@Getter
+public class BusinessException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    private final ResultCode resultCode;
+
+    public BusinessException(String message) {
+        super(message);
+        this.resultCode = ResultCode.BUSINESS_ERROR;
+    }
+
+    public BusinessException(ResultCode resultCode) {
+        super(resultCode.getMessage());
+        this.resultCode = resultCode;
+    }
+
+    public BusinessException(ResultCode resultCode, String message) {
+        super(message);
+        this.resultCode = resultCode;
+    }
+}

+ 96 - 0
position-common/src/main/java/com/example/position/common/exception/GlobalExceptionHandler.java

@@ -0,0 +1,96 @@
+package com.example.position.common.exception;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.exception.NotPermissionException;
+import cn.dev33.satoken.exception.NotRoleException;
+import com.example.position.common.api.Result;
+import com.example.position.common.enums.ResultCode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(BusinessException.class)
+    public Result<Void> handleBusinessException(BusinessException e) {
+        log.warn("业务异常: {}", e.getMessage());
+        return Result.fail(e.getResultCode(), e.getMessage());
+    }
+
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public Result<Void> handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
+        String message = e.getBindingResult().getFieldErrors().stream()
+                .map(FieldError::getDefaultMessage)
+                .collect(Collectors.joining("; "));
+        log.warn("参数校验失败: {}", message);
+        return Result.fail(ResultCode.PARAM_INVALID, message);
+    }
+
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(BindException.class)
+    public Result<Void> handleBindException(BindException e) {
+        String message = e.getBindingResult().getFieldErrors().stream()
+                .map(FieldError::getDefaultMessage)
+                .collect(Collectors.joining("; "));
+        log.warn("参数绑定失败: {}", message);
+        return Result.fail(ResultCode.PARAM_INVALID, message);
+    }
+
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(ConstraintViolationException.class)
+    public Result<Void> handleConstraintViolation(ConstraintViolationException e) {
+        String message = e.getConstraintViolations().stream()
+                .map(ConstraintViolation::getMessage)
+                .collect(Collectors.joining("; "));
+        log.warn("约束校验失败: {}", message);
+        return Result.fail(ResultCode.PARAM_INVALID, message);
+    }
+
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(MissingServletRequestParameterException.class)
+    public Result<Void> handleMissingParam(MissingServletRequestParameterException e) {
+        log.warn("缺少请求参数: {}", e.getParameterName());
+        return Result.fail(ResultCode.PARAM_MISSING, "缺少参数: " + e.getParameterName());
+    }
+
+    @ResponseStatus(HttpStatus.UNAUTHORIZED)
+    @ExceptionHandler(NotLoginException.class)
+    public Result<Void> handleNotLogin(NotLoginException e) {
+        log.warn("未登录访问: {}", e.getMessage());
+        return Result.fail(ResultCode.UNAUTHORIZED);
+    }
+
+    @ResponseStatus(HttpStatus.FORBIDDEN)
+    @ExceptionHandler(NotPermissionException.class)
+    public Result<Void> handleNotPermission(NotPermissionException e) {
+        log.warn("无权限访问: code={}", e.getCode());
+        return Result.fail(ResultCode.FORBIDDEN, "缺少权限: " + e.getCode());
+    }
+
+    @ResponseStatus(HttpStatus.FORBIDDEN)
+    @ExceptionHandler(NotRoleException.class)
+    public Result<Void> handleNotRole(NotRoleException e) {
+        log.warn("无角色权限: role={}", e.getRole());
+        return Result.fail(ResultCode.FORBIDDEN, "缺少角色: " + e.getRole());
+    }
+
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    @ExceptionHandler(Exception.class)
+    public Result<Void> handleException(Exception e) {
+        log.error("系统异常", e);
+        return Result.fail(ResultCode.SYSTEM_ERROR, e.getMessage());
+    }
+}

BIN
position-common/target/classes/com/example/position/common/api/Result.class


BIN
position-common/target/classes/com/example/position/common/config/CorsConfig.class


BIN
position-common/target/classes/com/example/position/common/config/SaTokenAuthConfig.class


BIN
position-common/target/classes/com/example/position/common/enums/ResultCode.class


BIN
position-common/target/classes/com/example/position/common/exception/BusinessException.class


BIN
position-common/target/classes/com/example/position/common/exception/GlobalExceptionHandler.class


+ 6 - 0
position-common/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

@@ -0,0 +1,6 @@
+com\example\position\common\exception\BusinessException.class
+com\example\position\common\config\CorsConfig.class
+com\example\position\common\config\SaTokenAuthConfig.class
+com\example\position\common\enums\ResultCode.class
+com\example\position\common\exception\GlobalExceptionHandler.class
+com\example\position\common\api\Result.class

+ 6 - 0
position-common/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,6 @@
+D:\A-work\position-system\position-common\src\main\java\com\example\position\common\config\CorsConfig.java
+D:\A-work\position-system\position-common\src\main\java\com\example\position\common\exception\GlobalExceptionHandler.java
+D:\A-work\position-system\position-common\src\main\java\com\example\position\common\api\Result.java
+D:\A-work\position-system\position-common\src\main\java\com\example\position\common\config\SaTokenAuthConfig.java
+D:\A-work\position-system\position-common\src\main\java\com\example\position\common\exception\BusinessException.java
+D:\A-work\position-system\position-common\src\main\java\com\example\position\common\enums\ResultCode.java

+ 103 - 0
position-service/pom.xml

@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.example</groupId>
+        <artifactId>position-management-system</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>position-service</artifactId>
+    <packaging>jar</packaging>
+    <name>position-service</name>
+    <description>头寸管理微服务</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.example</groupId>
+            <artifactId>position-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.oracle.database.jdbc</groupId>
+            <artifactId>ojdbc8</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.oracle.database.nls</groupId>
+            <artifactId>orai18n</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-ui</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct-processor</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 13 - 0
position-service/src/main/java/com/example/position/Application.java

@@ -0,0 +1,13 @@
+package com.example.position;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@MapperScan(basePackages = "com.example.position.infrastructure.mapper")
+public class Application {
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+}

+ 27 - 0
position-service/src/main/java/com/example/position/controller/HealthController.java

@@ -0,0 +1,27 @@
+package com.example.position.controller;
+
+import com.example.position.common.api.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Tag(name = "系统", description = "健康检查、系统信息")
+@RestController
+@RequestMapping("/api/health")
+public class HealthController {
+
+    @Operation(summary = "健康检查", description = "返回服务运行状态")
+    @GetMapping
+    public Result<Map<String, Object>> health() {
+        Map<String, Object> info = new LinkedHashMap<>();
+        info.put("status", "UP");
+        info.put("service", "position-service");
+        info.put("timestamp", System.currentTimeMillis());
+        return Result.success(info);
+    }
+}

+ 71 - 0
position-service/src/main/java/com/example/position/controller/PositionController.java

@@ -0,0 +1,71 @@
+package com.example.position.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.example.position.service.dto.PositionCreateDTO;
+import com.example.position.service.dto.PositionQueryDTO;
+import com.example.position.service.dto.PositionResponseDTO;
+import com.example.position.service.dto.PositionUpdateDTO;
+import com.example.position.service.PositionApplicationService;
+import com.example.position.common.api.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Tag(name = "头寸管理", description = "头寸的增删改查接口")
+@RestController
+@RequestMapping("/api/positions")
+@RequiredArgsConstructor
+public class PositionController {
+
+    private final PositionApplicationService positionApplicationService;
+
+    @Operation(summary = "创建头寸", description = "创建新的头寸记录")
+    @PostMapping
+    public Result<PositionResponseDTO> create(@RequestBody PositionCreateDTO createDTO) {
+        return Result.success(positionApplicationService.createPosition(createDTO));
+    }
+
+    @Operation(summary = "更新头寸", description = "根据ID更新头寸信息")
+    @PutMapping("/{id}")
+    public Result<PositionResponseDTO> update(
+            @Parameter(description = "头寸ID") @PathVariable Long id,
+            @RequestBody PositionUpdateDTO updateDTO) {
+        return Result.success(positionApplicationService.updatePosition(id, updateDTO));
+    }
+
+    @Operation(summary = "删除头寸", description = "根据ID删除头寸(逻辑删除)")
+    @DeleteMapping("/{id}")
+    public Result<Void> delete(@Parameter(description = "头寸ID") @PathVariable Long id) {
+        positionApplicationService.deletePosition(id);
+        return Result.success();
+    }
+
+    @Operation(summary = "根据ID查询", description = "根据ID查询单个头寸详情")
+    @GetMapping("/{id}")
+    public Result<PositionResponseDTO> findById(@Parameter(description = "头寸ID") @PathVariable Long id) {
+        return Result.success(positionApplicationService.findById(id));
+    }
+
+    @Operation(summary = "根据编号查询", description = "根据头寸编号查询")
+    @GetMapping("/code/{positionCode}")
+    public Result<PositionResponseDTO> findByCode(
+            @Parameter(description = "头寸编号") @PathVariable String positionCode) {
+        return Result.success(positionApplicationService.findByCode(positionCode));
+    }
+
+    @Operation(summary = "查询全部", description = "查询所有头寸(不分页,数据量大时请用分页接口)")
+    @GetMapping
+    public Result<List<PositionResponseDTO>> findAll() {
+        return Result.success(positionApplicationService.findAll());
+    }
+
+    @Operation(summary = "分页查询", description = "按条件分页查询头寸列表")
+    @GetMapping("/page")
+    public Result<Page<PositionResponseDTO>> pageQuery(PositionQueryDTO queryDTO) {
+        return Result.success(positionApplicationService.pageQuery(queryDTO));
+    }
+}

+ 67 - 0
position-service/src/main/java/com/example/position/domain/entity/Position.java

@@ -0,0 +1,67 @@
+package com.example.position.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.Version;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("T_POSITION")
+public class Position implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "ID", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    @TableField("POSITION_CODE")
+    private String positionCode;
+
+    @TableField("POSITION_NAME")
+    private String positionName;
+
+    @TableField("POSITION_TYPE")
+    private String positionType;
+
+    @TableField("DIRECTION")
+    private String direction;
+
+    @TableField("QUANTITY")
+    private BigDecimal quantity;
+
+    @TableField("PRICE")
+    private BigDecimal price;
+
+    @TableField("MARKET_VALUE")
+    private BigDecimal marketValue;
+
+    @TableField("ACCOUNT_CODE")
+    private String accountCode;
+
+    @TableField("PRODUCT_CODE")
+    private String productCode;
+
+    @TableField("STATUS")
+    private String status;
+
+    @Version
+    @TableField("VERSION")
+    private Integer version;
+
+    @TableLogic
+    @TableField("DELETED")
+    private Integer deleted;
+
+    @TableField("CREATE_TIME")
+    private LocalDateTime createTime;
+
+    @TableField("UPDATE_TIME")
+    private LocalDateTime updateTime;
+}

+ 24 - 0
position-service/src/main/java/com/example/position/domain/repository/PositionRepository.java

@@ -0,0 +1,24 @@
+package com.example.position.domain.repository;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.example.position.domain.entity.Position;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface PositionRepository {
+
+    Position save(Position position);
+
+    Position update(Position position);
+
+    void deleteById(Long id);
+
+    Optional<Position> findById(Long id);
+
+    Optional<Position> findByCode(String positionCode);
+
+    List<Position> findAll();
+
+    Page<Position> pageQuery(Position query, int pageNum, int pageSize);
+}

+ 30 - 0
position-service/src/main/java/com/example/position/infrastructure/config/DruidConfig.java

@@ -0,0 +1,30 @@
+package com.example.position.infrastructure.config;
+
+import com.alibaba.druid.support.http.StatViewServlet;
+import com.alibaba.druid.support.http.WebStatFilter;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class DruidConfig {
+
+    @Bean
+    public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
+        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
+        bean.addInitParameter("loginUsername", "admin");
+        bean.addInitParameter("loginPassword", "admin");
+        bean.addInitParameter("allow", "");
+        bean.addInitParameter("deny", "");
+        return bean;
+    }
+
+    @Bean
+    public FilterRegistrationBean<WebStatFilter> druidWebStatFilter() {
+        FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>(new WebStatFilter());
+        bean.addUrlPatterns("/*");
+        bean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
+        return bean;
+    }
+}

+ 20 - 0
position-service/src/main/java/com/example/position/infrastructure/config/MyBatisPlusConfig.java

@@ -0,0 +1,20 @@
+package com.example.position.infrastructure.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MyBatisPlusConfig {
+
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.ORACLE));
+        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
+        return interceptor;
+    }
+}

+ 23 - 0
position-service/src/main/java/com/example/position/infrastructure/config/OpenApiConfig.java

@@ -0,0 +1,23 @@
+package com.example.position.infrastructure.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class OpenApiConfig {
+
+    @Bean
+    public OpenAPI positionOpenAPI() {
+        return new OpenAPI()
+                .info(new Info()
+                        .title("头寸管理系统 API 文档")
+                        .description("Position Management System - REST API 接口文档")
+                        .version("v1.0.0")
+                        .contact(new Contact().name("Position Team"))
+                        .license(new License().name("Internal Use Only")));
+    }
+}

+ 12 - 0
position-service/src/main/java/com/example/position/infrastructure/mapper/PositionMapper.java

@@ -0,0 +1,12 @@
+package com.example.position.infrastructure.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.position.domain.entity.Position;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+@Mapper
+public interface PositionMapper extends BaseMapper<Position> {
+
+    Position findByCode(@Param("positionCode") String positionCode);
+}

+ 95 - 0
position-service/src/main/java/com/example/position/infrastructure/repository/PositionRepositoryImpl.java

@@ -0,0 +1,95 @@
+package com.example.position.infrastructure.repository;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.example.position.domain.entity.Position;
+import com.example.position.domain.repository.PositionRepository;
+import com.example.position.infrastructure.mapper.PositionMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Repository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+@Slf4j
+@Repository
+@RequiredArgsConstructor
+public class PositionRepositoryImpl implements PositionRepository {
+
+    private final PositionMapper positionMapper;
+
+    @Override
+    public Position save(Position position) {
+        if (position.getCreateTime() == null) {
+            position.setCreateTime(LocalDateTime.now());
+        }
+        if (position.getUpdateTime() == null) {
+            position.setUpdateTime(LocalDateTime.now());
+        }
+        if (position.getDeleted() == null) {
+            position.setDeleted(0);
+        }
+        positionMapper.insert(position);
+        return position;
+    }
+
+    @Override
+    public Position update(Position position) {
+        position.setUpdateTime(LocalDateTime.now());
+        positionMapper.updateById(position);
+        return position;
+    }
+
+    @Override
+    public void deleteById(Long id) {
+        positionMapper.deleteById(id);
+    }
+
+    @Override
+    public Optional<Position> findById(Long id) {
+        return Optional.ofNullable(positionMapper.selectById(id));
+    }
+
+    @Override
+    public Optional<Position> findByCode(String positionCode) {
+        return Optional.ofNullable(positionMapper.findByCode(positionCode));
+    }
+
+    @Override
+    public List<Position> findAll() {
+        LambdaQueryWrapper<Position> wrapper = new LambdaQueryWrapper<>();
+        wrapper.orderByDesc(Position::getCreateTime);
+        return positionMapper.selectList(wrapper);
+    }
+
+    @Override
+    public Page<Position> pageQuery(Position query, int pageNum, int pageSize) {
+        Page<Position> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<Position> wrapper = new LambdaQueryWrapper<>();
+        if (query.getPositionCode() != null && !query.getPositionCode().isEmpty()) {
+            wrapper.like(Position::getPositionCode, query.getPositionCode());
+        }
+        if (query.getPositionName() != null && !query.getPositionName().isEmpty()) {
+            wrapper.like(Position::getPositionName, query.getPositionName());
+        }
+        if (query.getPositionType() != null && !query.getPositionType().isEmpty()) {
+            wrapper.eq(Position::getPositionType, query.getPositionType());
+        }
+        if (query.getDirection() != null && !query.getDirection().isEmpty()) {
+            wrapper.eq(Position::getDirection, query.getDirection());
+        }
+        if (query.getStatus() != null && !query.getStatus().isEmpty()) {
+            wrapper.eq(Position::getStatus, query.getStatus());
+        }
+        if (query.getAccountCode() != null && !query.getAccountCode().isEmpty()) {
+            wrapper.eq(Position::getAccountCode, query.getAccountCode());
+        }
+        if (query.getProductCode() != null && !query.getProductCode().isEmpty()) {
+            wrapper.eq(Position::getProductCode, query.getProductCode());
+        }
+        wrapper.orderByDesc(Position::getCreateTime);
+        return positionMapper.selectPage(page, wrapper);
+    }
+}

+ 26 - 0
position-service/src/main/java/com/example/position/service/PositionApplicationService.java

@@ -0,0 +1,26 @@
+package com.example.position.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.example.position.service.dto.PositionCreateDTO;
+import com.example.position.service.dto.PositionQueryDTO;
+import com.example.position.service.dto.PositionResponseDTO;
+import com.example.position.service.dto.PositionUpdateDTO;
+
+import java.util.List;
+
+public interface PositionApplicationService {
+
+    PositionResponseDTO createPosition(PositionCreateDTO createDTO);
+
+    PositionResponseDTO updatePosition(Long id, PositionUpdateDTO updateDTO);
+
+    void deletePosition(Long id);
+
+    PositionResponseDTO findById(Long id);
+
+    PositionResponseDTO findByCode(String positionCode);
+
+    List<PositionResponseDTO> findAll();
+
+    Page<PositionResponseDTO> pageQuery(PositionQueryDTO queryDTO);
+}

+ 46 - 0
position-service/src/main/java/com/example/position/service/converter/PositionConverter.java

@@ -0,0 +1,46 @@
+package com.example.position.service.converter;
+
+import com.example.position.service.dto.PositionCreateDTO;
+import com.example.position.service.dto.PositionQueryDTO;
+import com.example.position.service.dto.PositionResponseDTO;
+import com.example.position.service.dto.PositionUpdateDTO;
+import com.example.position.domain.entity.Position;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+@Mapper
+public interface PositionConverter {
+
+    PositionConverter INSTANCE = Mappers.getMapper(PositionConverter.class);
+
+    @Mapping(target = "id", ignore = true)
+    @Mapping(target = "version", ignore = true)
+    @Mapping(target = "deleted", ignore = true)
+    @Mapping(target = "createTime", ignore = true)
+    @Mapping(target = "updateTime", ignore = true)
+    Position toEntity(PositionCreateDTO createDTO);
+
+    @Mapping(target = "createTime", ignore = true)
+    @Mapping(target = "updateTime", ignore = true)
+    @Mapping(target = "deleted", ignore = true)
+    Position toEntity(PositionUpdateDTO updateDTO);
+
+    @Mapping(target = "id", ignore = true)
+    @Mapping(target = "version", ignore = true)
+    @Mapping(target = "deleted", ignore = true)
+    @Mapping(target = "quantity", ignore = true)
+    @Mapping(target = "price", ignore = true)
+    @Mapping(target = "marketValue", ignore = true)
+    @Mapping(target = "accountCode", ignore = true)
+    @Mapping(target = "productCode", ignore = true)
+    @Mapping(target = "createTime", ignore = true)
+    @Mapping(target = "updateTime", ignore = true)
+    Position toEntityFromQuery(PositionQueryDTO queryDTO);
+
+    PositionResponseDTO toResponse(Position entity);
+
+    List<PositionResponseDTO> toResponseList(List<Position> entities);
+}

+ 32 - 0
position-service/src/main/java/com/example/position/service/dto/PositionCreateDTO.java

@@ -0,0 +1,32 @@
+package com.example.position.service.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class PositionCreateDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String positionCode;
+
+    private String positionName;
+
+    private String positionType;
+
+    private String direction;
+
+    private BigDecimal quantity;
+
+    private BigDecimal price;
+
+    private BigDecimal marketValue;
+
+    private String accountCode;
+
+    private String productCode;
+
+    private String status;
+}

+ 29 - 0
position-service/src/main/java/com/example/position/service/dto/PositionQueryDTO.java

@@ -0,0 +1,29 @@
+package com.example.position.service.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class PositionQueryDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String positionCode;
+
+    private String positionName;
+
+    private String positionType;
+
+    private String direction;
+
+    private String status;
+
+    private String accountCode;
+
+    private String productCode;
+
+    private Integer pageNum = 1;
+
+    private Integer pageSize = 10;
+}

+ 28 - 0
position-service/src/main/java/com/example/position/service/dto/PositionResponseDTO.java

@@ -0,0 +1,28 @@
+package com.example.position.service.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+public class PositionResponseDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+    private String positionCode;
+    private String positionName;
+    private String positionType;
+    private String direction;
+    private BigDecimal quantity;
+    private BigDecimal price;
+    private BigDecimal marketValue;
+    private String accountCode;
+    private String productCode;
+    private String status;
+    private Integer version;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 32 - 0
position-service/src/main/java/com/example/position/service/dto/PositionUpdateDTO.java

@@ -0,0 +1,32 @@
+package com.example.position.service.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class PositionUpdateDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String positionCode;
+
+    private String positionName;
+
+    private String positionType;
+
+    private String direction;
+
+    private BigDecimal quantity;
+
+    private BigDecimal price;
+
+    private BigDecimal marketValue;
+
+    private String accountCode;
+
+    private String productCode;
+
+    private String status;
+}

+ 113 - 0
position-service/src/main/java/com/example/position/service/impl/PositionApplicationServiceImpl.java

@@ -0,0 +1,113 @@
+package com.example.position.service.impl;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.example.position.service.converter.PositionConverter;
+import com.example.position.service.dto.PositionCreateDTO;
+import com.example.position.service.dto.PositionQueryDTO;
+import com.example.position.service.dto.PositionResponseDTO;
+import com.example.position.service.dto.PositionUpdateDTO;
+import com.example.position.service.PositionApplicationService;
+import com.example.position.common.enums.ResultCode;
+import com.example.position.common.exception.BusinessException;
+import com.example.position.domain.entity.Position;
+import com.example.position.domain.repository.PositionRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class PositionApplicationServiceImpl implements PositionApplicationService {
+
+    private final PositionRepository positionRepository;
+
+    @Override
+    @Transactional
+    public PositionResponseDTO createPosition(PositionCreateDTO createDTO) {
+        log.info("创建头寸: positionCode={}", createDTO.getPositionCode());
+
+        positionRepository.findByCode(createDTO.getPositionCode())
+                .ifPresent(p -> {
+                    throw new BusinessException(ResultCode.DATA_DUPLICATED,
+                            "头寸编号已存在: " + createDTO.getPositionCode());
+                });
+
+        Position entity = PositionConverter.INSTANCE.toEntity(createDTO);
+        Position saved = positionRepository.save(entity);
+
+        Position result = positionRepository.findById(saved.getId())
+                .orElse(saved);
+        return PositionConverter.INSTANCE.toResponse(result);
+    }
+
+    @Override
+    @Transactional
+    public PositionResponseDTO updatePosition(Long id, PositionUpdateDTO updateDTO) {
+        log.info("更新头寸: id={}", id);
+
+        Position existing = positionRepository.findById(id)
+                .orElseThrow(() -> new BusinessException(ResultCode.DATA_NOT_FOUND,
+                        "头寸不存在: id=" + id));
+
+        Position entity = PositionConverter.INSTANCE.toEntity(updateDTO);
+        entity.setId(id);
+        entity.setVersion(existing.getVersion());
+
+        positionRepository.update(entity);
+
+        Position updated = positionRepository.findById(id)
+                .orElseThrow(() -> new BusinessException(ResultCode.DATA_NOT_FOUND,
+                        "头寸不存在: id=" + id));
+        return PositionConverter.INSTANCE.toResponse(updated);
+    }
+
+    @Override
+    @Transactional
+    public void deletePosition(Long id) {
+        log.info("删除头寸: id={}", id);
+
+        positionRepository.findById(id)
+                .orElseThrow(() -> new BusinessException(ResultCode.DATA_NOT_FOUND,
+                        "头寸不存在: id=" + id));
+
+        positionRepository.deleteById(id);
+    }
+
+    @Override
+    public PositionResponseDTO findById(Long id) {
+        return positionRepository.findById(id)
+                .map(PositionConverter.INSTANCE::toResponse)
+                .orElseThrow(() -> new BusinessException(ResultCode.DATA_NOT_FOUND,
+                        "头寸不存在: id=" + id));
+    }
+
+    @Override
+    public PositionResponseDTO findByCode(String positionCode) {
+        return positionRepository.findByCode(positionCode)
+                .map(PositionConverter.INSTANCE::toResponse)
+                .orElseThrow(() -> new BusinessException(ResultCode.DATA_NOT_FOUND,
+                        "头寸不存在: positionCode=" + positionCode));
+    }
+
+    @Override
+    public List<PositionResponseDTO> findAll() {
+        List<Position> entities = positionRepository.findAll();
+        return PositionConverter.INSTANCE.toResponseList(entities);
+    }
+
+    @Override
+    public Page<PositionResponseDTO> pageQuery(PositionQueryDTO queryDTO) {
+        Position query = PositionConverter.INSTANCE.toEntityFromQuery(queryDTO);
+        Page<Position> page = positionRepository.pageQuery(
+                query, queryDTO.getPageNum(), queryDTO.getPageSize());
+
+        List<PositionResponseDTO> records = PositionConverter.INSTANCE.toResponseList(page.getRecords());
+        Page<PositionResponseDTO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
+        result.setRecords(records);
+        return result;
+    }
+}

+ 16 - 0
position-service/src/main/resources/application-local.yml

@@ -0,0 +1,16 @@
+spring:
+  datasource:
+    url: jdbc:oracle:thin:@localhost:1521:ORCL
+    username: ${DB_USERNAME:system}
+    password: ${DB_PASSWORD:Root123456}
+  autoconfigure:
+    exclude:
+      - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
+      - org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration
+
+sa-token:
+  enable-auth: false
+
+logging:
+  level:
+    com.example.position: debug

+ 74 - 0
position-service/src/main/resources/application.yml

@@ -0,0 +1,74 @@
+server:
+  port: 8080
+  servlet:
+    context-path: /
+
+spring:
+  application:
+    name: position-service
+  profiles:
+    active: ${SPRING_PROFILES_ACTIVE:local}
+  datasource:
+    driver-class-name: oracle.jdbc.OracleDriver
+    type: com.alibaba.druid.pool.DruidDataSource
+    druid:
+      initial-size: 5
+      min-idle: 5
+      max-active: 20
+      max-wait: 60000
+      time-between-eviction-runs-millis: 60000
+      min-evictable-idle-time-millis: 300000
+      validation-query: SELECT 1 FROM DUAL
+      test-while-idle: true
+      test-on-borrow: false
+      test-on-return: false
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 20
+      filter:
+        stat:
+          enabled: true
+          log-slow-sql: true
+          slow-sql-millis: 1000
+        wall:
+          enabled: true
+
+mybatis-plus:
+  mapper-locations: classpath*:mapper/**/*.xml
+  configuration:
+    map-underscore-to-camel-case: true
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    db-config:
+      logic-delete-field: deleted
+      logic-delete-value: 1
+      logic-not-delete-value: 0
+
+sa-token:
+  token-name: Authorization
+  timeout: 2592000
+  active-timeout: -1
+  is-concurrent: true
+  is-share: false
+  token-style: uuid
+  is-log: false
+
+springdoc:
+  api-docs:
+    path: /v3/api-docs
+  swagger-ui:
+    path: /swagger-ui.html
+    tags-sorter: alpha
+    operations-sorter: alpha
+  packages-to-scan: com.example.position.controller
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: health,info,prometheus,metrics
+  endpoint:
+    health:
+      show-details: always
+  metrics:
+    tags:
+      application: ${spring.application.name}

+ 36 - 0
position-service/src/main/resources/mapper/PositionMapper.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.example.position.infrastructure.mapper.PositionMapper">
+
+    <resultMap id="BaseResultMap" type="com.example.position.domain.entity.Position">
+        <id column="ID" property="id"/>
+        <result column="POSITION_CODE" property="positionCode"/>
+        <result column="POSITION_NAME" property="positionName"/>
+        <result column="POSITION_TYPE" property="positionType"/>
+        <result column="DIRECTION" property="direction"/>
+        <result column="QUANTITY" property="quantity"/>
+        <result column="PRICE" property="price"/>
+        <result column="MARKET_VALUE" property="marketValue"/>
+        <result column="ACCOUNT_CODE" property="accountCode"/>
+        <result column="PRODUCT_CODE" property="productCode"/>
+        <result column="STATUS" property="status"/>
+        <result column="VERSION" property="version"/>
+        <result column="DELETED" property="deleted"/>
+        <result column="CREATE_TIME" property="createTime"/>
+        <result column="UPDATE_TIME" property="updateTime"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        ID, POSITION_CODE, POSITION_NAME, POSITION_TYPE, DIRECTION,
+        QUANTITY, PRICE, MARKET_VALUE, ACCOUNT_CODE, PRODUCT_CODE,
+        STATUS, VERSION, DELETED, CREATE_TIME, UPDATE_TIME
+    </sql>
+
+    <select id="findByCode" resultMap="BaseResultMap">
+        SELECT <include refid="Base_Column_List"/>
+        FROM T_POSITION
+        WHERE POSITION_CODE = #{positionCode}
+          AND DELETED = 0
+    </select>
+
+</mapper>

+ 50 - 0
position-service/src/main/resources/sql/schema-oracle.sql

@@ -0,0 +1,50 @@
+-- =============================================
+-- 头寸管理系统 - 建表脚本 (Oracle 19c)
+-- =============================================
+
+-- 头寸表
+CREATE TABLE T_POSITION (
+    ID              NUMBER(19)    NOT NULL,
+    POSITION_CODE   VARCHAR2(64)  NOT NULL,
+    POSITION_NAME   VARCHAR2(128) NOT NULL,
+    POSITION_TYPE   VARCHAR2(32)  NOT NULL,
+    DIRECTION       VARCHAR2(16)  NOT NULL,
+    QUANTITY        NUMBER(18,4)  DEFAULT 0,
+    PRICE           NUMBER(18,4)  DEFAULT 0,
+    MARKET_VALUE    NUMBER(18,4)  DEFAULT 0,
+    ACCOUNT_CODE    VARCHAR2(64),
+    PRODUCT_CODE    VARCHAR2(64),
+    STATUS          VARCHAR2(16)  DEFAULT 'ACTIVE',
+    VERSION         NUMBER(10)    DEFAULT 0,
+    DELETED         NUMBER(1)     DEFAULT 0,
+    CREATE_TIME     TIMESTAMP     DEFAULT SYSDATE,
+    UPDATE_TIME     TIMESTAMP     DEFAULT SYSDATE,
+    CONSTRAINT PK_T_POSITION PRIMARY KEY (ID),
+    CONSTRAINT UK_T_POSITION_CODE UNIQUE (POSITION_CODE)
+);
+
+COMMENT ON TABLE  T_POSITION              IS '头寸表';
+COMMENT ON COLUMN T_POSITION.ID              IS '主键ID';
+COMMENT ON COLUMN T_POSITION.POSITION_CODE   IS '头寸编号(业务唯一编号)';
+COMMENT ON COLUMN T_POSITION.POSITION_NAME   IS '头寸名称';
+COMMENT ON COLUMN T_POSITION.POSITION_TYPE   IS '头寸类型:TRADING-交易头寸,INVESTMENT-投资头寸,SETTLEMENT-结算头寸';
+COMMENT ON COLUMN T_POSITION.DIRECTION      IS '方向:LONG-多头,SHORT-空头';
+COMMENT ON COLUMN T_POSITION.QUANTITY       IS '数量';
+COMMENT ON COLUMN T_POSITION.PRICE           IS '价格';
+COMMENT ON COLUMN T_POSITION.MARKET_VALUE    IS '市值';
+COMMENT ON COLUMN T_POSITION.ACCOUNT_CODE    IS '账户编号';
+COMMENT ON COLUMN T_POSITION.PRODUCT_CODE    IS '产品编号';
+COMMENT ON COLUMN T_POSITION.STATUS          IS '状态:ACTIVE-有效,CLOSED-已平仓';
+COMMENT ON COLUMN T_POSITION.VERSION         IS '乐观锁版本号';
+COMMENT ON COLUMN T_POSITION.DELETED         IS '逻辑删除:0-未删除,1-已删除';
+COMMENT ON COLUMN T_POSITION.CREATE_TIME     IS '创建时间';
+COMMENT ON COLUMN T_POSITION.UPDATE_TIME     IS '更新时间';
+
+-- 创建序列(Oracle 自增ID)
+CREATE SEQUENCE SEQ_T_POSITION START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;
+
+-- 创建索引
+CREATE INDEX IDX_T_POSITION_TYPE     ON T_POSITION(POSITION_TYPE);
+CREATE INDEX IDX_T_POSITION_STATUS   ON T_POSITION(STATUS);
+CREATE INDEX IDX_T_POSITION_ACCOUNT  ON T_POSITION(ACCOUNT_CODE);
+CREATE INDEX IDX_T_POSITION_PRODUCT  ON T_POSITION(PRODUCT_CODE);

+ 272 - 0
position-service/src/test/java/com/example/position/controller/PositionControllerTest.java

@@ -0,0 +1,272 @@
+package com.example.position.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.example.position.common.api.Result;
+import com.example.position.common.enums.ResultCode;
+import com.example.position.common.exception.BusinessException;
+import com.example.position.common.exception.GlobalExceptionHandler;
+import com.example.position.service.PositionApplicationService;
+import com.example.position.service.dto.PositionResponseDTO;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+/**
+ * PositionController 接口测试
+ * 使用 standaloneSetup 不加载 Spring 上下文,纯 Mock 测试
+ */
+@ExtendWith(MockitoExtension.class)
+@DisplayName("头寸接口测试")
+class PositionControllerTest {
+
+    private MockMvc mockMvc;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Mock
+    private PositionApplicationService positionApplicationService;
+
+    @InjectMocks
+    private PositionController positionController;
+
+    @BeforeEach
+    void setUp() {
+        mockMvc = MockMvcBuilders.standaloneSetup(positionController)
+                .setControllerAdvice(new GlobalExceptionHandler())
+                .build();
+    }
+
+    private PositionResponseDTO buildResponse() {
+        PositionResponseDTO dto = new PositionResponseDTO();
+        dto.setId(1L);
+        dto.setPositionCode("POS001");
+        dto.setPositionName("测试头寸");
+        dto.setPositionType("TRADING");
+        dto.setDirection("LONG");
+        dto.setQuantity(new BigDecimal("1000"));
+        dto.setPrice(new BigDecimal("12.50"));
+        dto.setMarketValue(new BigDecimal("12500"));
+        dto.setAccountCode("ACC001");
+        dto.setProductCode("PROD001");
+        dto.setStatus("ACTIVE");
+        dto.setVersion(0);
+        dto.setCreateTime(LocalDateTime.of(2026, 8, 10, 12, 0, 0));
+        dto.setUpdateTime(LocalDateTime.of(2026, 8, 10, 12, 0, 0));
+        return dto;
+    }
+
+    // ==================== 创建 ====================
+
+    @Nested
+    @DisplayName("POST /api/positions - 创建头寸")
+    class Create {
+
+        @Test
+        @DisplayName("成功创建 - 返回200和数据")
+        void shouldReturn200WhenCreate() throws Exception {
+            String body = """
+                    {
+                      "positionCode": "POS001",
+                      "positionName": "测试头寸",
+                      "positionType": "TRADING",
+                      "direction": "LONG",
+                      "quantity": 1000,
+                      "price": 12.50
+                    }
+                    """;
+
+            when(positionApplicationService.createPosition(any()))
+                    .thenReturn(buildResponse());
+
+            mockMvc.perform(post("/api/positions")
+                            .contentType(MediaType.APPLICATION_JSON)
+                            .content(body))
+                    .andExpect(status().isOk())
+                    .andExpect(jsonPath("$.code").value(200))
+                    .andExpect(jsonPath("$.success").value(true))
+                    .andExpect(jsonPath("$.data.positionCode").value("POS001"))
+                    .andExpect(jsonPath("$.data.positionName").value("测试头寸"));
+        }
+
+        @Test
+        @DisplayName("创建失败 - 编号重复时返回业务错误")
+        void shouldReturnErrorWhenDuplicate() throws Exception {
+            String body = """
+                    { "positionCode": "POS001", "positionName": "重复" }
+                    """;
+
+            when(positionApplicationService.createPosition(any()))
+                    .thenThrow(new BusinessException(ResultCode.DATA_DUPLICATED, "头寸编号已存在: POS001"));
+
+            mockMvc.perform(post("/api/positions")
+                            .contentType(MediaType.APPLICATION_JSON)
+                            .content(body))
+                    .andExpect(status().isOk())
+                    .andExpect(jsonPath("$.code").value(ResultCode.DATA_DUPLICATED.getCode()))
+                    .andExpect(jsonPath("$.success").value(false));
+        }
+    }
+
+    // ==================== 更新 ====================
+
+    @Nested
+    @DisplayName("PUT /api/positions/{id} - 更新头寸")
+    class Update {
+
+        @Test
+        @DisplayName("成功更新 - 返回200和更新后数据")
+        void shouldReturn200WhenUpdate() throws Exception {
+            String body = """
+                    { "positionName": "已修改", "quantity": 2000 }
+                    """;
+
+            PositionResponseDTO updated = buildResponse();
+            updated.setPositionName("已修改");
+            updated.setQuantity(new BigDecimal("2000"));
+            updated.setVersion(1);
+
+            when(positionApplicationService.updatePosition(eq(1L), any()))
+                    .thenReturn(updated);
+
+            mockMvc.perform(put("/api/positions/1")
+                            .contentType(MediaType.APPLICATION_JSON)
+                            .content(body))
+                    .andExpect(status().isOk())
+                    .andExpect(jsonPath("$.code").value(200))
+                    .andExpect(jsonPath("$.data.positionName").value("已修改"))
+                    .andExpect(jsonPath("$.data.quantity").value(2000))
+                    .andExpect(jsonPath("$.data.version").value(1));
+        }
+
+        @Test
+        @DisplayName("更新失败 - 头寸不存在时返回错误")
+        void shouldReturnErrorWhenNotFound() throws Exception {
+            when(positionApplicationService.updatePosition(eq(99L), any()))
+                    .thenThrow(new BusinessException(ResultCode.DATA_NOT_FOUND, "头寸不存在: id=99"));
+
+            mockMvc.perform(put("/api/positions/99")
+                            .contentType(MediaType.APPLICATION_JSON)
+                            .content("{}"))
+                    .andExpect(status().isOk())
+                    .andExpect(jsonPath("$.code").value(ResultCode.DATA_NOT_FOUND.getCode()))
+                    .andExpect(jsonPath("$.success").value(false));
+        }
+    }
+
+    // ==================== 删除 ====================
+
+    @Nested
+    @DisplayName("DELETE /api/positions/{id} - 删除头寸")
+    class Delete {
+
+        @Test
+        @DisplayName("成功删除 - 返回200")
+        void shouldReturn200WhenDelete() throws Exception {
+            doNothing().when(positionApplicationService).deletePosition(1L);
+
+            mockMvc.perform(delete("/api/positions/1"))
+                    .andExpect(status().isOk())
+                    .andExpect(jsonPath("$.code").value(200))
+                    .andExpect(jsonPath("$.success").value(true))
+                    .andExpect(jsonPath("$.data").doesNotExist());
+        }
+
+        @Test
+        @DisplayName("删除失败 - 头寸不存在时返回错误")
+        void shouldReturnErrorWhenDeleteNotFound() throws Exception {
+            doThrow(new BusinessException(ResultCode.DATA_NOT_FOUND, "头寸不存在: id=99"))
+                    .when(positionApplicationService).deletePosition(99L);
+
+            mockMvc.perform(delete("/api/positions/99"))
+                    .andExpect(status().isOk())
+                    .andExpect(jsonPath("$.code").value(ResultCode.DATA_NOT_FOUND.getCode()))
+                    .andExpect(jsonPath("$.success").value(false));
+        }
+    }
+
+    // ==================== 查询 ====================
+
+    @Nested
+    @DisplayName("GET 接口 - 查询头寸")
+    class Query {
+
+        @Test
+        @DisplayName("根据ID查询 - 返回200和数据")
+        void shouldReturn200WhenFindById() throws Exception {
+            when(positionApplicationService.findById(1L)).thenReturn(buildResponse());
+
+            mockMvc.perform(get("/api/positions/1"))
+                    .andExpect(status().isOk())
+                    .andExpect(jsonPath("$.data.id").value(1))
+                    .andExpect(jsonPath("$.data.positionCode").value("POS001"));
+        }
+
+        @Test
+        @DisplayName("根据ID查询 - 不存在时返回错误")
+        void shouldReturnErrorWhenFindByIdNotFound() throws Exception {
+            when(positionApplicationService.findById(99L))
+                    .thenThrow(new BusinessException(ResultCode.DATA_NOT_FOUND, "头寸不存在"));
+
+            mockMvc.perform(get("/api/positions/99"))
+                    .andExpect(jsonPath("$.code").value(ResultCode.DATA_NOT_FOUND.getCode()))
+                    .andExpect(jsonPath("$.success").value(false));
+        }
+
+        @Test
+        @DisplayName("根据编号查询 - 返回200和数据")
+        void shouldReturn200WhenFindByCode() throws Exception {
+            when(positionApplicationService.findByCode("POS001")).thenReturn(buildResponse());
+
+            mockMvc.perform(get("/api/positions/code/POS001"))
+                    .andExpect(status().isOk())
+                    .andExpect(jsonPath("$.data.positionCode").value("POS001"));
+        }
+
+        @Test
+        @DisplayName("查询全部 - 返回列表")
+        void shouldReturn200WhenFindAll() throws Exception {
+            when(positionApplicationService.findAll()).thenReturn(List.of(buildResponse()));
+
+            mockMvc.perform(get("/api/positions"))
+                    .andExpect(status().isOk())
+                    .andExpect(jsonPath("$.code").value(200))
+                    .andExpect(jsonPath("$.data[0].positionCode").value("POS001"));
+        }
+
+        @Test
+        @DisplayName("分页查询 - 返回分页数据")
+        void shouldReturn200WhenPageQuery() throws Exception {
+            Page<PositionResponseDTO> page = new Page<>(1, 10, 1);
+            page.setRecords(List.of(buildResponse()));
+
+            when(positionApplicationService.pageQuery(any())).thenReturn(page);
+
+            mockMvc.perform(get("/api/positions/page")
+                            .param("pageNum", "1")
+                            .param("pageSize", "10"))
+                    .andExpect(status().isOk())
+                    .andExpect(jsonPath("$.data.total").value(1))
+                    .andExpect(jsonPath("$.data.records[0].positionCode").value("POS001"))
+                    .andExpect(jsonPath("$.data.current").value(1));
+        }
+    }
+}

+ 264 - 0
position-service/src/test/java/com/example/position/service/impl/PositionApplicationServiceImplTest.java

@@ -0,0 +1,264 @@
+package com.example.position.service.impl;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.example.position.common.exception.BusinessException;
+import com.example.position.domain.entity.Position;
+import com.example.position.domain.repository.PositionRepository;
+import com.example.position.service.dto.PositionCreateDTO;
+import com.example.position.service.dto.PositionQueryDTO;
+import com.example.position.service.dto.PositionUpdateDTO;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+/**
+ * PositionApplicationServiceImpl 单元测试
+ * 使用 Mockito mock PositionRepository,不依赖数据库
+ */
+@ExtendWith(MockitoExtension.class)
+@DisplayName("头寸应用服务测试")
+class PositionApplicationServiceImplTest {
+
+    @Mock
+    private PositionRepository positionRepository;
+
+    @InjectMocks
+    private PositionApplicationServiceImpl service;
+
+    private Position testPosition;
+
+    @BeforeEach
+    void setUp() {
+        testPosition = new Position();
+        testPosition.setId(1L);
+        testPosition.setPositionCode("POS001");
+        testPosition.setPositionName("测试头寸");
+        testPosition.setPositionType("TRADING");
+        testPosition.setDirection("LONG");
+        testPosition.setQuantity(new BigDecimal("1000"));
+        testPosition.setPrice(new BigDecimal("12.50"));
+        testPosition.setMarketValue(new BigDecimal("12500"));
+        testPosition.setAccountCode("ACC001");
+        testPosition.setProductCode("PROD001");
+        testPosition.setStatus("ACTIVE");
+        testPosition.setVersion(0);
+        testPosition.setDeleted(0);
+    }
+
+    // ==================== 创建头寸 ====================
+
+    @Nested
+    @DisplayName("创建头寸")
+    class CreatePosition {
+
+        @Test
+        @DisplayName("成功创建 - 编号不存在时正常创建")
+        void shouldCreateWhenCodeNotExists() {
+            PositionCreateDTO dto = new PositionCreateDTO();
+            dto.setPositionCode("POS001");
+            dto.setPositionName("测试头寸");
+            dto.setPositionType("TRADING");
+            dto.setDirection("LONG");
+            dto.setQuantity(new BigDecimal("1000"));
+
+            when(positionRepository.findByCode("POS001")).thenReturn(Optional.empty());
+            when(positionRepository.save(any(Position.class))).thenAnswer(inv -> {
+                Position p = inv.getArgument(0);
+                p.setId(1L);
+                return p;
+            });
+            when(positionRepository.findById(1L)).thenReturn(Optional.of(testPosition));
+
+            var result = service.createPosition(dto);
+
+            assertNotNull(result);
+            assertEquals("POS001", result.getPositionCode());
+            assertEquals("测试头寸", result.getPositionName());
+            verify(positionRepository).save(any(Position.class));
+        }
+
+        @Test
+        @DisplayName("创建失败 - 编号已存在时抛出 BusinessException")
+        void shouldThrowWhenCodeExists() {
+            PositionCreateDTO dto = new PositionCreateDTO();
+            dto.setPositionCode("POS001");
+
+            when(positionRepository.findByCode("POS001")).thenReturn(Optional.of(testPosition));
+
+            BusinessException ex = assertThrows(BusinessException.class,
+                    () -> service.createPosition(dto));
+            assertTrue(ex.getMessage().contains("头寸编号已存在"));
+            verify(positionRepository, never()).save(any());
+        }
+    }
+
+    // ==================== 更新头寸 ====================
+
+    @Nested
+    @DisplayName("更新头寸")
+    class UpdatePosition {
+
+        @Test
+        @DisplayName("成功更新 - 头寸存在时正常更新")
+        void shouldUpdateWhenExists() {
+            PositionUpdateDTO dto = new PositionUpdateDTO();
+            dto.setPositionName("已修改");
+            dto.setQuantity(new BigDecimal("2000"));
+
+            Position updated = new Position();
+            updated.setId(1L);
+            updated.setPositionCode("POS001");
+            updated.setPositionName("已修改");
+            updated.setQuantity(new BigDecimal("2000"));
+            updated.setVersion(1);
+
+            when(positionRepository.findById(1L)).thenReturn(Optional.of(testPosition))
+                    .thenReturn(Optional.of(updated));
+
+            var result = service.updatePosition(1L, dto);
+
+            assertEquals("已修改", result.getPositionName());
+            assertEquals(new BigDecimal("2000"), result.getQuantity());
+            verify(positionRepository).update(any(Position.class));
+        }
+
+        @Test
+        @DisplayName("更新失败 - 头寸不存在时抛出 BusinessException")
+        void shouldThrowWhenNotFound() {
+            PositionUpdateDTO dto = new PositionUpdateDTO();
+            dto.setPositionName("已修改");
+
+            when(positionRepository.findById(99L)).thenReturn(Optional.empty());
+
+            BusinessException ex = assertThrows(BusinessException.class,
+                    () -> service.updatePosition(99L, dto));
+            assertTrue(ex.getMessage().contains("头寸不存在"));
+            verify(positionRepository, never()).update(any());
+        }
+    }
+
+    // ==================== 删除头寸 ====================
+
+    @Nested
+    @DisplayName("删除头寸")
+    class DeletePosition {
+
+        @Test
+        @DisplayName("成功删除 - 头寸存在时正常删除")
+        void shouldDeleteWhenExists() {
+            when(positionRepository.findById(1L)).thenReturn(Optional.of(testPosition));
+
+            assertDoesNotThrow(() -> service.deletePosition(1L));
+            verify(positionRepository).deleteById(1L);
+        }
+
+        @Test
+        @DisplayName("删除失败 - 头寸不存在时抛出 BusinessException")
+        void shouldThrowWhenDeleteNotFound() {
+            when(positionRepository.findById(99L)).thenReturn(Optional.empty());
+
+            assertThrows(BusinessException.class, () -> service.deletePosition(99L));
+            verify(positionRepository, never()).deleteById(any());
+        }
+    }
+
+    // ==================== 查询头寸 ====================
+
+    @Nested
+    @DisplayName("查询头寸")
+    class QueryPosition {
+
+        @Test
+        @DisplayName("根据ID查询 - 存在时返回数据")
+        void shouldFindByIdWhenExists() {
+            when(positionRepository.findById(1L)).thenReturn(Optional.of(testPosition));
+
+            var result = service.findById(1L);
+
+            assertEquals(1L, result.getId());
+            assertEquals("POS001", result.getPositionCode());
+        }
+
+        @Test
+        @DisplayName("根据ID查询 - 不存在时抛出异常")
+        void shouldThrowWhenFindByIdNotFound() {
+            when(positionRepository.findById(99L)).thenReturn(Optional.empty());
+
+            assertThrows(BusinessException.class, () -> service.findById(99L));
+        }
+
+        @Test
+        @DisplayName("根据编号查询 - 存在时返回数据")
+        void shouldFindByCodeWhenExists() {
+            when(positionRepository.findByCode("POS001")).thenReturn(Optional.of(testPosition));
+
+            var result = service.findByCode("POS001");
+
+            assertEquals("POS001", result.getPositionCode());
+        }
+
+        @Test
+        @DisplayName("根据编号查询 - 不存在时抛出异常")
+        void shouldThrowWhenFindByCodeNotFound() {
+            when(positionRepository.findByCode("NOTEXIST")).thenReturn(Optional.empty());
+
+            assertThrows(BusinessException.class, () -> service.findByCode("NOTEXIST"));
+        }
+
+        @Test
+        @DisplayName("查询全部 - 返回列表")
+        void shouldFindAll() {
+            when(positionRepository.findAll()).thenReturn(List.of(testPosition));
+
+            var results = service.findAll();
+
+            assertEquals(1, results.size());
+            assertEquals("POS001", results.get(0).getPositionCode());
+        }
+
+        @Test
+        @DisplayName("查询全部 - 无数据时返回空列表")
+        void shouldFindAllEmpty() {
+            when(positionRepository.findAll()).thenReturn(Collections.emptyList());
+
+            var results = service.findAll();
+
+            assertTrue(results.isEmpty());
+        }
+
+        @Test
+        @DisplayName("分页查询 - 返回分页数据")
+        void shouldPageQuery() {
+            PositionQueryDTO query = new PositionQueryDTO();
+            query.setPageNum(1);
+            query.setPageSize(10);
+
+            Page<Position> page = new Page<>(1, 10, 1);
+            page.setRecords(List.of(testPosition));
+
+            when(positionRepository.pageQuery(any(Position.class), eq(1), eq(10)))
+                    .thenReturn(page);
+
+            var result = service.pageQuery(query);
+
+            assertEquals(1, result.getTotal());
+            assertEquals(1, result.getRecords().size());
+            assertEquals("POS001", result.getRecords().get(0).getPositionCode());
+        }
+    }
+}

+ 16 - 0
position-service/target/classes/application-local.yml

@@ -0,0 +1,16 @@
+spring:
+  datasource:
+    url: jdbc:oracle:thin:@localhost:1521:ORCL
+    username: ${DB_USERNAME:system}
+    password: ${DB_PASSWORD:Root123456}
+  autoconfigure:
+    exclude:
+      - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
+      - org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration
+
+sa-token:
+  enable-auth: false
+
+logging:
+  level:
+    com.example.position: debug

+ 74 - 0
position-service/target/classes/application.yml

@@ -0,0 +1,74 @@
+server:
+  port: 8080
+  servlet:
+    context-path: /
+
+spring:
+  application:
+    name: position-service
+  profiles:
+    active: ${SPRING_PROFILES_ACTIVE:local}
+  datasource:
+    driver-class-name: oracle.jdbc.OracleDriver
+    type: com.alibaba.druid.pool.DruidDataSource
+    druid:
+      initial-size: 5
+      min-idle: 5
+      max-active: 20
+      max-wait: 60000
+      time-between-eviction-runs-millis: 60000
+      min-evictable-idle-time-millis: 300000
+      validation-query: SELECT 1 FROM DUAL
+      test-while-idle: true
+      test-on-borrow: false
+      test-on-return: false
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 20
+      filter:
+        stat:
+          enabled: true
+          log-slow-sql: true
+          slow-sql-millis: 1000
+        wall:
+          enabled: true
+
+mybatis-plus:
+  mapper-locations: classpath*:mapper/**/*.xml
+  configuration:
+    map-underscore-to-camel-case: true
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    db-config:
+      logic-delete-field: deleted
+      logic-delete-value: 1
+      logic-not-delete-value: 0
+
+sa-token:
+  token-name: Authorization
+  timeout: 2592000
+  active-timeout: -1
+  is-concurrent: true
+  is-share: false
+  token-style: uuid
+  is-log: false
+
+springdoc:
+  api-docs:
+    path: /v3/api-docs
+  swagger-ui:
+    path: /swagger-ui.html
+    tags-sorter: alpha
+    operations-sorter: alpha
+  packages-to-scan: com.example.position.controller
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: health,info,prometheus,metrics
+  endpoint:
+    health:
+      show-details: always
+  metrics:
+    tags:
+      application: ${spring.application.name}

BIN
position-service/target/classes/com/example/position/Application.class


BIN
position-service/target/classes/com/example/position/controller/HealthController.class


BIN
position-service/target/classes/com/example/position/controller/PositionController.class


BIN
position-service/target/classes/com/example/position/domain/entity/Position.class


BIN
position-service/target/classes/com/example/position/domain/repository/PositionRepository.class


BIN
position-service/target/classes/com/example/position/infrastructure/config/DruidConfig.class


BIN
position-service/target/classes/com/example/position/infrastructure/config/MyBatisPlusConfig.class


BIN
position-service/target/classes/com/example/position/infrastructure/config/OpenApiConfig.class


BIN
position-service/target/classes/com/example/position/infrastructure/mapper/PositionMapper.class


BIN
position-service/target/classes/com/example/position/infrastructure/repository/PositionRepositoryImpl.class


BIN
position-service/target/classes/com/example/position/service/PositionApplicationService.class


BIN
position-service/target/classes/com/example/position/service/converter/PositionConverter.class


BIN
position-service/target/classes/com/example/position/service/converter/PositionConverterImpl.class


BIN
position-service/target/classes/com/example/position/service/dto/PositionCreateDTO.class


BIN
position-service/target/classes/com/example/position/service/dto/PositionQueryDTO.class


BIN
position-service/target/classes/com/example/position/service/dto/PositionResponseDTO.class


BIN
position-service/target/classes/com/example/position/service/dto/PositionUpdateDTO.class


BIN
position-service/target/classes/com/example/position/service/impl/PositionApplicationServiceImpl.class


+ 36 - 0
position-service/target/classes/mapper/PositionMapper.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.example.position.infrastructure.mapper.PositionMapper">
+
+    <resultMap id="BaseResultMap" type="com.example.position.domain.entity.Position">
+        <id column="ID" property="id"/>
+        <result column="POSITION_CODE" property="positionCode"/>
+        <result column="POSITION_NAME" property="positionName"/>
+        <result column="POSITION_TYPE" property="positionType"/>
+        <result column="DIRECTION" property="direction"/>
+        <result column="QUANTITY" property="quantity"/>
+        <result column="PRICE" property="price"/>
+        <result column="MARKET_VALUE" property="marketValue"/>
+        <result column="ACCOUNT_CODE" property="accountCode"/>
+        <result column="PRODUCT_CODE" property="productCode"/>
+        <result column="STATUS" property="status"/>
+        <result column="VERSION" property="version"/>
+        <result column="DELETED" property="deleted"/>
+        <result column="CREATE_TIME" property="createTime"/>
+        <result column="UPDATE_TIME" property="updateTime"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        ID, POSITION_CODE, POSITION_NAME, POSITION_TYPE, DIRECTION,
+        QUANTITY, PRICE, MARKET_VALUE, ACCOUNT_CODE, PRODUCT_CODE,
+        STATUS, VERSION, DELETED, CREATE_TIME, UPDATE_TIME
+    </sql>
+
+    <select id="findByCode" resultMap="BaseResultMap">
+        SELECT <include refid="Base_Column_List"/>
+        FROM T_POSITION
+        WHERE POSITION_CODE = #{positionCode}
+          AND DELETED = 0
+    </select>
+
+</mapper>

+ 50 - 0
position-service/target/classes/sql/schema-oracle.sql

@@ -0,0 +1,50 @@
+-- =============================================
+-- 头寸管理系统 - 建表脚本 (Oracle 19c)
+-- =============================================
+
+-- 头寸表
+CREATE TABLE T_POSITION (
+    ID              NUMBER(19)    NOT NULL,
+    POSITION_CODE   VARCHAR2(64)  NOT NULL,
+    POSITION_NAME   VARCHAR2(128) NOT NULL,
+    POSITION_TYPE   VARCHAR2(32)  NOT NULL,
+    DIRECTION       VARCHAR2(16)  NOT NULL,
+    QUANTITY        NUMBER(18,4)  DEFAULT 0,
+    PRICE           NUMBER(18,4)  DEFAULT 0,
+    MARKET_VALUE    NUMBER(18,4)  DEFAULT 0,
+    ACCOUNT_CODE    VARCHAR2(64),
+    PRODUCT_CODE    VARCHAR2(64),
+    STATUS          VARCHAR2(16)  DEFAULT 'ACTIVE',
+    VERSION         NUMBER(10)    DEFAULT 0,
+    DELETED         NUMBER(1)     DEFAULT 0,
+    CREATE_TIME     TIMESTAMP     DEFAULT SYSDATE,
+    UPDATE_TIME     TIMESTAMP     DEFAULT SYSDATE,
+    CONSTRAINT PK_T_POSITION PRIMARY KEY (ID),
+    CONSTRAINT UK_T_POSITION_CODE UNIQUE (POSITION_CODE)
+);
+
+COMMENT ON TABLE  T_POSITION              IS '头寸表';
+COMMENT ON COLUMN T_POSITION.ID              IS '主键ID';
+COMMENT ON COLUMN T_POSITION.POSITION_CODE   IS '头寸编号(业务唯一编号)';
+COMMENT ON COLUMN T_POSITION.POSITION_NAME   IS '头寸名称';
+COMMENT ON COLUMN T_POSITION.POSITION_TYPE   IS '头寸类型:TRADING-交易头寸,INVESTMENT-投资头寸,SETTLEMENT-结算头寸';
+COMMENT ON COLUMN T_POSITION.DIRECTION      IS '方向:LONG-多头,SHORT-空头';
+COMMENT ON COLUMN T_POSITION.QUANTITY       IS '数量';
+COMMENT ON COLUMN T_POSITION.PRICE           IS '价格';
+COMMENT ON COLUMN T_POSITION.MARKET_VALUE    IS '市值';
+COMMENT ON COLUMN T_POSITION.ACCOUNT_CODE    IS '账户编号';
+COMMENT ON COLUMN T_POSITION.PRODUCT_CODE    IS '产品编号';
+COMMENT ON COLUMN T_POSITION.STATUS          IS '状态:ACTIVE-有效,CLOSED-已平仓';
+COMMENT ON COLUMN T_POSITION.VERSION         IS '乐观锁版本号';
+COMMENT ON COLUMN T_POSITION.DELETED         IS '逻辑删除:0-未删除,1-已删除';
+COMMENT ON COLUMN T_POSITION.CREATE_TIME     IS '创建时间';
+COMMENT ON COLUMN T_POSITION.UPDATE_TIME     IS '更新时间';
+
+-- 创建序列(Oracle 自增ID)
+CREATE SEQUENCE SEQ_T_POSITION START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;
+
+-- 创建索引
+CREATE INDEX IDX_T_POSITION_TYPE     ON T_POSITION(POSITION_TYPE);
+CREATE INDEX IDX_T_POSITION_STATUS   ON T_POSITION(STATUS);
+CREATE INDEX IDX_T_POSITION_ACCOUNT  ON T_POSITION(ACCOUNT_CODE);
+CREATE INDEX IDX_T_POSITION_PRODUCT  ON T_POSITION(PRODUCT_CODE);

+ 119 - 0
position-service/target/generated-sources/annotations/com/example/position/service/converter/PositionConverterImpl.java

@@ -0,0 +1,119 @@
+package com.example.position.service.converter;
+
+import com.example.position.domain.entity.Position;
+import com.example.position.service.dto.PositionCreateDTO;
+import com.example.position.service.dto.PositionQueryDTO;
+import com.example.position.service.dto.PositionResponseDTO;
+import com.example.position.service.dto.PositionUpdateDTO;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.processing.Generated;
+
+@Generated(
+    value = "org.mapstruct.ap.MappingProcessor",
+    date = "2026-08-19T09:14:16+0800",
+    comments = "version: 1.5.5.Final, compiler: Eclipse JDT (IDE) 3.46.100.v20260624-0231, environment: Java 21.0.11 (Eclipse Adoptium)"
+)
+public class PositionConverterImpl implements PositionConverter {
+
+    @Override
+    public Position toEntity(PositionCreateDTO createDTO) {
+        if ( createDTO == null ) {
+            return null;
+        }
+
+        Position position = new Position();
+
+        position.setAccountCode( createDTO.getAccountCode() );
+        position.setDirection( createDTO.getDirection() );
+        position.setMarketValue( createDTO.getMarketValue() );
+        position.setPositionCode( createDTO.getPositionCode() );
+        position.setPositionName( createDTO.getPositionName() );
+        position.setPositionType( createDTO.getPositionType() );
+        position.setPrice( createDTO.getPrice() );
+        position.setProductCode( createDTO.getProductCode() );
+        position.setQuantity( createDTO.getQuantity() );
+        position.setStatus( createDTO.getStatus() );
+
+        return position;
+    }
+
+    @Override
+    public Position toEntity(PositionUpdateDTO updateDTO) {
+        if ( updateDTO == null ) {
+            return null;
+        }
+
+        Position position = new Position();
+
+        position.setAccountCode( updateDTO.getAccountCode() );
+        position.setDirection( updateDTO.getDirection() );
+        position.setMarketValue( updateDTO.getMarketValue() );
+        position.setPositionCode( updateDTO.getPositionCode() );
+        position.setPositionName( updateDTO.getPositionName() );
+        position.setPositionType( updateDTO.getPositionType() );
+        position.setPrice( updateDTO.getPrice() );
+        position.setProductCode( updateDTO.getProductCode() );
+        position.setQuantity( updateDTO.getQuantity() );
+        position.setStatus( updateDTO.getStatus() );
+
+        return position;
+    }
+
+    @Override
+    public Position toEntityFromQuery(PositionQueryDTO queryDTO) {
+        if ( queryDTO == null ) {
+            return null;
+        }
+
+        Position position = new Position();
+
+        position.setDirection( queryDTO.getDirection() );
+        position.setPositionCode( queryDTO.getPositionCode() );
+        position.setPositionName( queryDTO.getPositionName() );
+        position.setPositionType( queryDTO.getPositionType() );
+        position.setStatus( queryDTO.getStatus() );
+
+        return position;
+    }
+
+    @Override
+    public PositionResponseDTO toResponse(Position entity) {
+        if ( entity == null ) {
+            return null;
+        }
+
+        PositionResponseDTO positionResponseDTO = new PositionResponseDTO();
+
+        positionResponseDTO.setAccountCode( entity.getAccountCode() );
+        positionResponseDTO.setCreateTime( entity.getCreateTime() );
+        positionResponseDTO.setDirection( entity.getDirection() );
+        positionResponseDTO.setId( entity.getId() );
+        positionResponseDTO.setMarketValue( entity.getMarketValue() );
+        positionResponseDTO.setPositionCode( entity.getPositionCode() );
+        positionResponseDTO.setPositionName( entity.getPositionName() );
+        positionResponseDTO.setPositionType( entity.getPositionType() );
+        positionResponseDTO.setPrice( entity.getPrice() );
+        positionResponseDTO.setProductCode( entity.getProductCode() );
+        positionResponseDTO.setQuantity( entity.getQuantity() );
+        positionResponseDTO.setStatus( entity.getStatus() );
+        positionResponseDTO.setUpdateTime( entity.getUpdateTime() );
+        positionResponseDTO.setVersion( entity.getVersion() );
+
+        return positionResponseDTO;
+    }
+
+    @Override
+    public List<PositionResponseDTO> toResponseList(List<Position> entities) {
+        if ( entities == null ) {
+            return null;
+        }
+
+        List<PositionResponseDTO> list = new ArrayList<PositionResponseDTO>( entities.size() );
+        for ( Position position : entities ) {
+            list.add( toResponse( position ) );
+        }
+
+        return list;
+    }
+}

+ 18 - 0
position-service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

@@ -0,0 +1,18 @@
+com\example\position\infrastructure\config\MyBatisPlusConfig.class
+com\example\position\service\dto\PositionCreateDTO.class
+com\example\position\service\PositionApplicationService.class
+com\example\position\controller\PositionController.class
+com\example\position\service\dto\PositionUpdateDTO.class
+com\example\position\infrastructure\config\OpenApiConfig.class
+com\example\position\infrastructure\mapper\PositionMapper.class
+com\example\position\service\converter\PositionConverterImpl.class
+com\example\position\domain\entity\Position.class
+com\example\position\infrastructure\repository\PositionRepositoryImpl.class
+com\example\position\service\impl\PositionApplicationServiceImpl.class
+com\example\position\domain\repository\PositionRepository.class
+com\example\position\service\dto\PositionResponseDTO.class
+com\example\position\controller\HealthController.class
+com\example\position\service\converter\PositionConverter.class
+com\example\position\service\dto\PositionQueryDTO.class
+com\example\position\infrastructure\config\DruidConfig.class
+com\example\position\Application.class

+ 17 - 0
position-service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,17 @@
+D:\A-work\position-system\position-service\src\main\java\com\example\position\infrastructure\repository\PositionRepositoryImpl.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\service\dto\PositionResponseDTO.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\service\dto\PositionCreateDTO.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\controller\PositionController.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\domain\entity\Position.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\infrastructure\config\DruidConfig.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\controller\HealthController.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\domain\repository\PositionRepository.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\infrastructure\config\MyBatisPlusConfig.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\infrastructure\mapper\PositionMapper.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\service\dto\PositionQueryDTO.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\service\impl\PositionApplicationServiceImpl.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\Application.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\service\converter\PositionConverter.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\service\PositionApplicationService.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\infrastructure\config\OpenApiConfig.java
+D:\A-work\position-system\position-service\src\main\java\com\example\position\service\dto\PositionUpdateDTO.java

+ 10 - 0
position-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst

@@ -0,0 +1,10 @@
+com\example\position\controller\PositionControllerTest$Query.class
+com\example\position\controller\PositionControllerTest$Delete.class
+com\example\position\service\impl\PositionApplicationServiceImplTest$UpdatePosition.class
+com\example\position\controller\PositionControllerTest$Update.class
+com\example\position\service\impl\PositionApplicationServiceImplTest$QueryPosition.class
+com\example\position\service\impl\PositionApplicationServiceImplTest$DeletePosition.class
+com\example\position\controller\PositionControllerTest$Create.class
+com\example\position\service\impl\PositionApplicationServiceImplTest.class
+com\example\position\controller\PositionControllerTest.class
+com\example\position\service\impl\PositionApplicationServiceImplTest$CreatePosition.class

+ 2 - 0
position-service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst

@@ -0,0 +1,2 @@
+D:\A-work\position-system\position-service\src\test\java\com\example\position\controller\PositionControllerTest.java
+D:\A-work\position-system\position-service\src\test\java\com\example\position\service\impl\PositionApplicationServiceImplTest.java

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 6 - 0
position-service/target/surefire-reports/TEST-com.example.position.controller.PositionControllerTest.xml


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 6 - 0
position-service/target/surefire-reports/TEST-com.example.position.service.impl.PositionApplicationServiceImplTest.xml


+ 4 - 0
position-service/target/surefire-reports/com.example.position.controller.PositionControllerTest.txt

@@ -0,0 +1,4 @@
+-------------------------------------------------------------------------------
+Test set: com.example.position.controller.PositionControllerTest
+-------------------------------------------------------------------------------
+Tests run: 11, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.788 s - in com.example.position.controller.PositionControllerTest

+ 4 - 0
position-service/target/surefire-reports/com.example.position.service.impl.PositionApplicationServiceImplTest.txt

@@ -0,0 +1,4 @@
+-------------------------------------------------------------------------------
+Test set: com.example.position.service.impl.PositionApplicationServiceImplTest
+-------------------------------------------------------------------------------
+Tests run: 13, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.026 s - in com.example.position.service.impl.PositionApplicationServiceImplTest

BIN
position-service/target/test-classes/com/example/position/controller/PositionControllerTest$Create.class


BIN
position-service/target/test-classes/com/example/position/controller/PositionControllerTest$Delete.class


BIN
position-service/target/test-classes/com/example/position/controller/PositionControllerTest$Query.class


BIN
position-service/target/test-classes/com/example/position/controller/PositionControllerTest$Update.class


BIN
position-service/target/test-classes/com/example/position/controller/PositionControllerTest.class


BIN
position-service/target/test-classes/com/example/position/service/impl/PositionApplicationServiceImplTest$CreatePosition.class


BIN
position-service/target/test-classes/com/example/position/service/impl/PositionApplicationServiceImplTest$DeletePosition.class


BIN
position-service/target/test-classes/com/example/position/service/impl/PositionApplicationServiceImplTest$QueryPosition.class


BIN
position-service/target/test-classes/com/example/position/service/impl/PositionApplicationServiceImplTest$UpdatePosition.class


BIN
position-service/target/test-classes/com/example/position/service/impl/PositionApplicationServiceImplTest.class


+ 12 - 0
position-ui/index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>头寸管理系统</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 12 - 0
position-ui/node_modules/.bin/acorn

@@ -0,0 +1,12 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -x "$basedir/node" ]; then
+  exec "$basedir/node"  "$basedir/../acorn/bin/acorn" "$@"
+else 
+  exec node  "$basedir/../acorn/bin/acorn" "$@"
+fi

+ 17 - 0
position-ui/node_modules/.bin/acorn.cmd

@@ -0,0 +1,17 @@
+@ECHO off
+GOTO start
+:find_dp0
+SET dp0=%~dp0
+EXIT /b
+:start
+SETLOCAL
+CALL :find_dp0
+
+IF EXIST "%dp0%\node.exe" (
+  SET "_prog=%dp0%\node.exe"
+) ELSE (
+  SET "_prog=node"
+  SET PATHEXT=%PATHEXT:;.JS;=;%
+)
+
+endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%"  "%dp0%\..\acorn\bin\acorn" %*

+ 28 - 0
position-ui/node_modules/.bin/acorn.ps1

@@ -0,0 +1,28 @@
+#!/usr/bin/env pwsh
+$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
+
+$exe=""
+if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
+  # Fix case when both the Windows and Linux builds of Node
+  # are installed in the same directory
+  $exe=".exe"
+}
+$ret=0
+if (Test-Path "$basedir/node$exe") {
+  # Support pipeline input
+  if ($MyInvocation.ExpectingInput) {
+    $input | & "$basedir/node$exe"  "$basedir/../acorn/bin/acorn" $args
+  } else {
+    & "$basedir/node$exe"  "$basedir/../acorn/bin/acorn" $args
+  }
+  $ret=$LASTEXITCODE
+} else {
+  # Support pipeline input
+  if ($MyInvocation.ExpectingInput) {
+    $input | & "node$exe"  "$basedir/../acorn/bin/acorn" $args
+  } else {
+    & "node$exe"  "$basedir/../acorn/bin/acorn" $args
+  }
+  $ret=$LASTEXITCODE
+}
+exit $ret

+ 12 - 0
position-ui/node_modules/.bin/esbuild

@@ -0,0 +1,12 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -x "$basedir/node" ]; then
+  exec "$basedir/node"  "$basedir/../esbuild/bin/esbuild" "$@"
+else 
+  exec node  "$basedir/../esbuild/bin/esbuild" "$@"
+fi

+ 17 - 0
position-ui/node_modules/.bin/esbuild.cmd

@@ -0,0 +1,17 @@
+@ECHO off
+GOTO start
+:find_dp0
+SET dp0=%~dp0
+EXIT /b
+:start
+SETLOCAL
+CALL :find_dp0
+
+IF EXIST "%dp0%\node.exe" (
+  SET "_prog=%dp0%\node.exe"
+) ELSE (
+  SET "_prog=node"
+  SET PATHEXT=%PATHEXT:;.JS;=;%
+)
+
+endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%"  "%dp0%\..\esbuild\bin\esbuild" %*

+ 28 - 0
position-ui/node_modules/.bin/esbuild.ps1

@@ -0,0 +1,28 @@
+#!/usr/bin/env pwsh
+$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
+
+$exe=""
+if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
+  # Fix case when both the Windows and Linux builds of Node
+  # are installed in the same directory
+  $exe=".exe"
+}
+$ret=0
+if (Test-Path "$basedir/node$exe") {
+  # Support pipeline input
+  if ($MyInvocation.ExpectingInput) {
+    $input | & "$basedir/node$exe"  "$basedir/../esbuild/bin/esbuild" $args
+  } else {
+    & "$basedir/node$exe"  "$basedir/../esbuild/bin/esbuild" $args
+  }
+  $ret=$LASTEXITCODE
+} else {
+  # Support pipeline input
+  if ($MyInvocation.ExpectingInput) {
+    $input | & "node$exe"  "$basedir/../esbuild/bin/esbuild" $args
+  } else {
+    & "node$exe"  "$basedir/../esbuild/bin/esbuild" $args
+  }
+  $ret=$LASTEXITCODE
+}
+exit $ret

+ 12 - 0
position-ui/node_modules/.bin/nanoid

@@ -0,0 +1,12 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -x "$basedir/node" ]; then
+  exec "$basedir/node"  "$basedir/../nanoid/bin/nanoid.cjs" "$@"
+else 
+  exec node  "$basedir/../nanoid/bin/nanoid.cjs" "$@"
+fi

+ 17 - 0
position-ui/node_modules/.bin/nanoid.cmd

@@ -0,0 +1,17 @@
+@ECHO off
+GOTO start
+:find_dp0
+SET dp0=%~dp0
+EXIT /b
+:start
+SETLOCAL
+CALL :find_dp0
+
+IF EXIST "%dp0%\node.exe" (
+  SET "_prog=%dp0%\node.exe"
+) ELSE (
+  SET "_prog=node"
+  SET PATHEXT=%PATHEXT:;.JS;=;%
+)
+
+endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%"  "%dp0%\..\nanoid\bin\nanoid.cjs" %*

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно