Compare commits
221 Commits
220d61d328
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99741d44b5 | ||
|
|
62adbd70a4 | ||
|
|
6c31b71f9f | ||
|
|
9173a7ac20 | ||
|
|
4a648d53e9 | ||
|
|
e97c8e00c5 | ||
|
|
a2271b4e0d | ||
|
|
4430d77c81 | ||
|
|
2f05b86f74 | ||
|
|
cc63fece65 | ||
|
|
269d9e5857 | ||
|
|
37a6ec63ba | ||
|
|
7f49c0cdc0 | ||
|
|
3833ed68db | ||
|
|
276be30387 | ||
|
|
9b061b8992 | ||
|
|
f5ecc9a151 | ||
|
|
7cd2ea11da | ||
|
|
19f9c84718 | ||
|
|
fede591197 | ||
|
|
075181cc32 | ||
|
|
e41b3a8dbc | ||
|
|
b9e840a2ba | ||
|
|
a1ea55dffa | ||
|
|
1a638eab5e | ||
|
|
76153930dc | ||
|
|
9f3aa79aa5 | ||
|
|
a573993365 | ||
|
|
6817626669 | ||
|
|
94c07397a0 | ||
|
|
87a037616e | ||
|
|
01c63e1b82 | ||
|
|
4191843802 | ||
|
|
a2c758abae | ||
|
|
6ca4489ad7 | ||
|
|
e9474e672a | ||
|
|
e0dcaf4ff6 | ||
|
|
902a6a9b75 | ||
|
|
97740d0447 | ||
|
|
291cf01983 | ||
|
|
1ccbc3c6d3 | ||
|
|
29f134c3e5 | ||
|
|
2293899780 | ||
|
|
5c32d8977c | ||
|
|
175dc327c3 | ||
|
|
5b58186c96 | ||
|
|
590cace08a | ||
|
|
6f8b1d9b2b | ||
|
|
e9945d67aa | ||
|
|
e716663731 | ||
|
|
30cfd98e92 | ||
|
|
261fbd7180 | ||
|
|
e2114845b5 | ||
|
|
abb1c8500c | ||
|
|
edade96d4a | ||
|
|
c8bb3d4ebd | ||
|
|
b2e89bf5bd | ||
|
|
8a8dd48726 | ||
|
|
b2882ed70a | ||
|
|
5fd2c2d38f | ||
|
|
ebe85f99ef | ||
|
|
2abd401b41 | ||
|
|
73c498ea37 | ||
|
|
e33a5e36ad | ||
|
|
01c6d0fda2 | ||
|
|
55df63985a | ||
|
|
088586f126 | ||
|
|
c661084bb8 | ||
|
|
edfc11d198 | ||
|
|
89031f86fe | ||
|
|
d05a4cb7e2 | ||
|
|
8b38fb2bb7 | ||
|
|
a6e2dda7a7 | ||
|
|
6571076ae6 | ||
|
|
fadd3d6ff3 | ||
|
|
6f32e781b6 | ||
|
|
c734d71c36 | ||
|
|
4a86c66b1c | ||
|
|
5870edb938 | ||
|
|
d1bee92563 | ||
|
|
53e55c1123 | ||
|
|
74e87033ed | ||
|
|
d2e8cd3bcc | ||
|
|
cca0e14823 | ||
|
|
fde1929b2c | ||
|
|
bcd87c3b73 | ||
|
|
53c4450d58 | ||
|
|
af33b34237 | ||
|
|
44e2d7c1f6 | ||
|
|
a47e544657 | ||
|
|
2f461a3a95 | ||
|
|
f13a58e116 | ||
|
|
8bedb4681f | ||
|
|
144042595c | ||
|
|
5592dabc62 | ||
|
|
55f10f344a | ||
|
|
3a7d40ae7b | ||
|
|
0e8afddcb7 | ||
|
|
c14ed2bf45 | ||
|
|
fed9de8090 | ||
|
|
79c631d356 | ||
|
|
e57d48568e | ||
|
|
a57a04c448 | ||
|
|
6ff0d6561e | ||
|
|
300d4d257d | ||
|
|
a67e76bcb8 | ||
|
|
1ebc924efb | ||
|
|
fcc758dd32 | ||
|
|
c2439db17f | ||
|
|
0175b0823e | ||
|
|
5c71c93eee | ||
|
|
03f14394e5 | ||
|
|
396f03964a | ||
|
|
a5753f0c47 | ||
|
|
578e1596ef | ||
|
|
c1870aa135 | ||
|
|
9104b740b2 | ||
|
|
59e45b0733 | ||
|
|
8c84f791da | ||
|
|
4b85656b21 | ||
|
|
b501038836 | ||
|
|
713b3b446a | ||
|
|
8c73afd3aa | ||
|
|
d6bb904e64 | ||
|
|
b7cb6b247b | ||
|
|
caa3ca2a81 | ||
|
|
3b3b1a8807 | ||
|
|
8f61c56e9c | ||
|
|
57798cd12c | ||
|
|
11a93e8d65 | ||
|
|
f4473e47d9 | ||
|
|
13450aa87d | ||
|
|
86553ed4ff | ||
|
|
a485a65395 | ||
|
|
9c217e5622 | ||
|
|
a6c8a1c122 | ||
|
|
396afbdb37 | ||
|
|
647e72829b | ||
|
|
99bbd34c43 | ||
|
|
81170f637e | ||
|
|
54e0a5a0c4 | ||
|
|
7322fee989 | ||
|
|
43fd114804 | ||
|
|
ba18a383aa | ||
|
|
7469b4766b | ||
|
|
d2673a2760 | ||
|
|
66db58898c | ||
|
|
e2a6cfc21c | ||
|
|
c5ce461c2d | ||
|
|
6cf728b5b6 | ||
|
|
c0b55c82df | ||
|
|
0f7f692c93 | ||
|
|
310173bc9f | ||
|
|
143a87c3dd | ||
|
|
9627c939f6 | ||
|
|
338e2603a6 | ||
|
|
2d0bdb6910 | ||
|
|
6d1f22897f | ||
|
|
81748a09c0 | ||
|
|
49c233a914 | ||
|
|
fb8261361b | ||
|
|
f5e3c60e8c | ||
|
|
f5ed709da3 | ||
|
|
4cdeb4b34f | ||
|
|
3c2da12cd8 | ||
|
|
15a5fba934 | ||
|
|
b14166b292 | ||
|
|
d0e3f8456d | ||
|
|
d24e69af92 | ||
|
|
b428d91f89 | ||
|
|
80f8d059f0 | ||
|
|
30564cacb2 | ||
|
|
55e47930b3 | ||
|
|
fe61b7c365 | ||
|
|
f75f26d14c | ||
|
|
c4b356bc8b | ||
|
|
f5a3255df8 | ||
|
|
e85f38ac81 | ||
|
|
a5b21c6be6 | ||
|
|
fd598df29c | ||
|
|
b6349b5e6d | ||
|
|
90c5290635 | ||
|
|
cc33c46ef3 | ||
|
|
3cd8527a7d | ||
|
|
b177f0647d | ||
|
|
5e4d7509fd | ||
|
|
cddd551df9 | ||
|
|
adfc289492 | ||
|
|
f0f79006b3 | ||
|
|
84daf92ae4 | ||
|
|
43da030031 | ||
|
|
330cf5bef1 | ||
|
|
a2e796257c | ||
|
|
02c3f73c83 | ||
|
|
6dd4a9a41a | ||
|
|
8159f03e8c | ||
|
|
dee763ab5b | ||
|
|
4a2f543aff | ||
|
|
b6c475a387 | ||
|
|
8fba06bca3 | ||
|
|
f5277b087b | ||
|
|
e5e82b8a8c | ||
|
|
92da92f76f | ||
|
|
d030e07e51 | ||
|
|
0ea63d2ba7 | ||
|
|
8d6c211767 | ||
|
|
89838b60be | ||
|
|
aefba7b6e1 | ||
|
|
4e077e9ca2 | ||
|
|
0f66f1a09b | ||
|
|
b01e82b507 | ||
|
|
b33759d31f | ||
|
|
97d260c256 | ||
|
|
aad37217f4 | ||
|
|
ef37d7899c | ||
|
|
47e9aa27bd | ||
|
|
6411e96a59 | ||
|
|
77908424e3 | ||
|
|
af7c3534b8 | ||
|
|
770f348952 | ||
|
|
a0ece129b8 |
40
.gitignore
vendored
@@ -1,7 +1,43 @@
|
||||
# ---> Xcode
|
||||
# ==============================
|
||||
# Mac 系统垃圾文件(重点过滤)
|
||||
# ==============================
|
||||
# 桌面服务存储文件
|
||||
.DS_Store
|
||||
# 文件夹缩略图缓存
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
# 磁盘检测日志
|
||||
.com.apple.timemachine.donotpresent
|
||||
# 旧版 macOS 缓存
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
# 扩展属性文件
|
||||
._*
|
||||
# 网络磁盘缓存
|
||||
.networkTrashFolder
|
||||
# 空文件夹占位符(避免误提交空目录)
|
||||
.empty
|
||||
|
||||
# ==============================
|
||||
# XCode
|
||||
# ==============================
|
||||
## User settings
|
||||
xcuserdata/
|
||||
|
||||
# ==============================
|
||||
# Go
|
||||
# ==============================
|
||||
shared_data/
|
||||
|
||||
app.log
|
||||
|
||||
# ==============================
|
||||
# 编辑器配置
|
||||
# ==============================
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
119
backend/README.md
Normal file
@@ -0,0 +1,119 @@
|
||||
curl -X POST "http://127.0.0.1:20000/user/register" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"account": "test",
|
||||
"password": "test12341234"
|
||||
}'
|
||||
---
|
||||
# 创建美国
|
||||
curl -X POST "http://127.0.0.1:20000/country/create" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "美国", "code": "US"}'
|
||||
|
||||
# 创建中国
|
||||
curl -X POST "http://127.0.0.1:20000/country/create" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "中国", "code": "CN"}'
|
||||
|
||||
# 创建日本
|
||||
curl -X POST "http://127.0.0.1:20000/country/create" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "日本", "code": "JP"}'
|
||||
|
||||
# 创建香港
|
||||
curl -X POST "http://127.0.0.1:20000/country/create" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "香港", "code": "HK"}'
|
||||
---
|
||||
# 创建美国
|
||||
curl -X POST "http://127.0.0.1:20000/currency/create" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "美元", "code": "USD"}'
|
||||
|
||||
# 创建中国
|
||||
curl -X POST "http://127.0.0.1:20000/currency/create" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "人民币", "code": "CNY"}'
|
||||
|
||||
# 创建日本
|
||||
curl -X POST "http://127.0.0.1:20000/currency/create" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "日元", "code": "JPY"}'
|
||||
|
||||
# 创建香港
|
||||
curl -X POST "http://127.0.0.1:20000/currency/create" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "港币", "code": "HKD"}'
|
||||
---
|
||||
# 创建郑州商品交易所
|
||||
curl -X POST http://127.0.0.1:20000/exchange/create \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "郑州商品交易所",
|
||||
"short_name": "郑商所",
|
||||
"code": "CZCE"
|
||||
}'
|
||||
|
||||
# 创建上海证券交易所
|
||||
curl -X POST http://127.0.0.1:20000/exchange/create \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "上海证券交易所",
|
||||
"short_name": "上交所",
|
||||
"code": "SSE"
|
||||
}'
|
||||
|
||||
# 创建深圳证券交易所
|
||||
curl -X POST http://127.0.0.1:20000/exchange/create \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "深圳证券交易所",
|
||||
"short_name": "深交所",
|
||||
"code": "SZSE"
|
||||
}'
|
||||
|
||||
# 创建上海期货交易所
|
||||
curl -X POST http://127.0.0.1:20000/exchange/create \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "上海期货交易所",
|
||||
"short_name": "上期所",
|
||||
"code": "SHFE"
|
||||
}'
|
||||
|
||||
# 创建中国金融期货交易所
|
||||
curl -X POST http://127.0.0.1:20000/exchange/create \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "中国金融期货交易所",
|
||||
"short_name": "中金所",
|
||||
"code": "CFFEX"
|
||||
}'
|
||||
|
||||
# 创建大连商品期货交易所
|
||||
curl -X POST http://127.0.0.1:20000/exchange/create \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "大连商品交易所",
|
||||
"short_name": "大商所",
|
||||
"code": "DCE"
|
||||
}'
|
||||
|
||||
# 创建广州期货交易所
|
||||
curl -X POST http://127.0.0.1:20000/exchange/create \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "广州期货交易所",
|
||||
"short_name": "广期所",
|
||||
"code": "GFEX"
|
||||
}'
|
||||
|
||||
# 创建上海国际能源交易中心
|
||||
curl -X POST http://127.0.0.1:20000/exchange/create \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "上海国际能源交易中心",
|
||||
"short_name": "上期能源",
|
||||
"code": "INE"
|
||||
}'
|
||||
---
|
||||
12
backend/chat.md
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"biz":“新增国家“,
|
||||
"data": [
|
||||
{
|
||||
"id": "",
|
||||
"name": "",
|
||||
"code": "",
|
||||
"flag": "",
|
||||
}
|
||||
],
|
||||
}
|
||||
---
|
||||
@@ -15,11 +15,11 @@ log_error() {
|
||||
}
|
||||
|
||||
# 定义配置常量(等号两侧无空格!集中管理,便于修改)
|
||||
IMAGE_NAME="user-api"
|
||||
IMAGE_NAME="asset-assistant-api"
|
||||
IMAGE_TAG="1.0.0"
|
||||
FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
COMPOSE_PROJECT_NAME="user_service"
|
||||
DOCKER_COMPOSE_FILE="./docker-compose.yaml" # ✅ 关键修复:等号两侧无空格
|
||||
COMPOSE_PROJECT_NAME="asset_assistant_service"
|
||||
DOCKER_COMPOSE_FILE="./docker-compose.yaml"
|
||||
SRC_DIR="./src"
|
||||
DOCKERFILE_PATH="${SRC_DIR}/Dockerfile"
|
||||
|
||||
3
backend/dev-test.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
sudo docker stop go_asset_assistant_dev
|
||||
sudo docker rm go_asset_assistant_dev
|
||||
sudo docker run -itd --name go_asset_assistant_dev -v $(pwd)/src:/app -p 20010:80 golang:1.25.0-alpine3.22
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17.4-alpine
|
||||
container_name: user_db
|
||||
container_name: asset_assistant_db
|
||||
restart: always
|
||||
ports:
|
||||
- 20001:5432
|
||||
@@ -13,21 +13,21 @@ services:
|
||||
POSTGRES_DB: ${DB_NAME}
|
||||
TZ: ${TZ}
|
||||
volumes:
|
||||
- ./shared_data/user_db:/var/lib/postgresql/data
|
||||
- ./shared_data/asset_assistant_db:/var/lib/postgresql/data
|
||||
- ./sql:/docker-entrypoint-initdb.d
|
||||
- ./scripts:/scripts
|
||||
networks:
|
||||
- user-network
|
||||
user:
|
||||
- asset_assistant-network
|
||||
asset_assistant:
|
||||
image: golang:1.25.0-alpine3.22
|
||||
container_name: user_api
|
||||
container_name: asset_assistant_api
|
||||
restart: always
|
||||
ports:
|
||||
- 20000:80
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- user-network
|
||||
- asset_assistant-network
|
||||
environment:
|
||||
DB_HOST: postgres
|
||||
DB_PORT: ${DB_PORT}
|
||||
@@ -39,6 +39,6 @@ services:
|
||||
- ./src:/app
|
||||
command: sh -c "cd /app && go mod tidy && go run main.go"
|
||||
networks:
|
||||
user-network:
|
||||
asset_assistant-network:
|
||||
driver: bridge
|
||||
volumes: {}
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17.4-alpine
|
||||
container_name: user_db
|
||||
container_name: asset_assistant_db
|
||||
restart: always
|
||||
ports:
|
||||
- 20001:5432
|
||||
@@ -13,21 +13,21 @@ services:
|
||||
POSTGRES_DB: ${DB_NAME}
|
||||
TZ: ${TZ}
|
||||
volumes:
|
||||
- ./shared_data/user_db:/var/lib/postgresql/data
|
||||
- ./shared_data/asset_assistant_db:/var/lib/postgresql/data
|
||||
- ./sql:/docker-entrypoint-initdb.d
|
||||
- ./scripts:/scripts
|
||||
networks:
|
||||
- user-network
|
||||
user:
|
||||
image: user-api:1.0.0
|
||||
container_name: user_api
|
||||
- asset-assistant-network
|
||||
asset_assistant:
|
||||
image: asset-assistant-api:1.0.0
|
||||
container_name: asset_assistant_api
|
||||
restart: always
|
||||
ports:
|
||||
- 20000:80
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- user-network
|
||||
- asset-assistant-network
|
||||
environment:
|
||||
DB_HOST: postgres
|
||||
DB_PORT: ${DB_PORT}
|
||||
@@ -39,6 +39,6 @@ services:
|
||||
# 挂载添加日志目录挂载,将容器内日志日志目录映射到宿主机的 ./logs 目录
|
||||
- ./logs:/app/logs # 假设代码中日志存储路径为 /app/logs
|
||||
networks:
|
||||
user-network:
|
||||
asset-assistant-network:
|
||||
driver: bridge
|
||||
volumes: {}
|
||||
126
backend/prompt.md
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
分析这个项目,在 create.go 中完成以下需求:
|
||||
|
||||
1、接收 name,code 两个参数。
|
||||
2、确认提交的 name,code 两个参数不能为空,如果有空,则返回提示。
|
||||
3、第二步通过后,在 country 表中,通过: "INSERT INTO country DEFAULT VALUES RETURNING id" 获得ID。
|
||||
4、通过 3 中的 id,开启事务保存到 name 和 code 的表中。
|
||||
---
|
||||
分析这个项目,在 delete.go 中完成以下需求:
|
||||
|
||||
1、接收 country_id 参数。
|
||||
2、确认提交的 country_id 参数不能为空,如果有空,则返回提示。
|
||||
3、开启事务处理以下逻辑:
|
||||
3.1、把 country 中,country.id==req.country_id 的 deleted 字段更新为true。
|
||||
3.2、把 name 中,name.country_id==req.country_id 的 deleted 字段更新为true。
|
||||
3.3、把 code code.country_id==req.country_id 的 deleted 字段更新为true。
|
||||
---
|
||||
分析这个项目,在 update.go 中完成以下需求:
|
||||
|
||||
1、接收 country_id,name,code 参数。
|
||||
2、确认提交的 country_id 参数有不能为空,如果为空,则返回提示。
|
||||
3、确认提交的 name,code 两个参数,必须有一个不能为空,如果都为空,则返回提示。
|
||||
4、如果 name 不为空,开启事务保存到 name 中。
|
||||
5、如果 code 不为空,开启事务保存到 code 中。
|
||||
6、如果 name,code 都不为空,开启事务保存到 name,code 中。
|
||||
---
|
||||
分析这个项目,在 read.go 中完成以下需求:
|
||||
|
||||
1、接收 country_id,name,code,page,page_size 参数。
|
||||
2、确认提交的 country_id,name,code 必须有一个不能为空,如果都为空,则返回提示。
|
||||
3、确认提交的 page,page_size, 如果为空,则 page 默认为 1,page_size 默认为20。
|
||||
3、根据参数去 country_info_view 中查找数据,并做分页查询。
|
||||
4、将查找的数据分页返回。
|
||||
---
|
||||
读取./sql/08_trade.sql,然后多加一个子表,子表名叫trade_variety,子表逻辑字段为:
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL,
|
||||
variety_id UUID NOT NULL,
|
||||
variety_name VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
---
|
||||
读取./sql/08_trade.sql,然后多加一个子表,子表名叫trade_direction,子表逻辑字段为:
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL,
|
||||
direction VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
---
|
||||
读取./sql/08_trade.sql,然后多加一个子表,子表名叫trade_open_price,子表逻辑字段为:
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL,
|
||||
open_price NUMERIC(10,2) NOT NULL CHECK (price >= 0.00) DEFAULT 0.00,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
---
|
||||
读取./sql/08_trade.sql,然后多加一个子表,子表名叫trade_open_fee,子表逻辑字段为:
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL,
|
||||
open_fee NUMERIC(10,2) NOT NULL CHECK (price >= 0.00) DEFAULT 0.00,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
---
|
||||
读取./sql/08_trade.sql,然后多加一个子表,子表名叫trade_close_date,子表逻辑字段为:
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL,
|
||||
year INT NOT NULL DEFAULT 0,
|
||||
month INT NOT NULL DEFAULT 0,
|
||||
day INT NOT NULL DEFAULT 0,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
---
|
||||
读取./sql/08_trade.sql,然后多加一个子表,子表名叫trade_close_price,子表逻辑字段为:
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL,
|
||||
close_price NUMERIC(10,2) NOT NULL CHECK (price >= 0.00) DEFAULT 0.00,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
---
|
||||
读取./sql/08_trade.sql,完善trade_info_view视图逻辑,加入close_date和close_price信息。
|
||||
---
|
||||
读取./sql/08_trade.sql,然后多加一个子表,子表名叫trade_close_fee,子表逻辑字段为:
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL,
|
||||
close_fee NUMERIC(10,2) NOT NULL CHECK (close_fee >= 0.00) DEFAULT 0.00,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
---
|
||||
读取./sql/08_trade.sql,完善trade_info_view视图逻辑,加入trade_close_fee信息。
|
||||
---
|
||||
读取./sql/08_trade.sql,然后多加一个子表,子表名叫trade_profit,子表逻辑字段为:
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL,
|
||||
variety_tick NUMERIC(12,6) NOT NULL DEFAULT 0.00,
|
||||
variety_tick_price NUMERIC(12,6) NOT NULL CHECK (variety_tick_price >= 0.00) DEFAULT 0.00,
|
||||
win_tick NUMERIC(12,6) NOT NULL DEFAULT 0.00,
|
||||
win_tick_price NUMERIC(12,6) NOT NULL DEFAULT 0.00,
|
||||
fee_cost NUMERIC(12,6) NOT NULL DEFAULT 0.00,
|
||||
trade_win NUMERIC(12,6) NOT NULL DEFAULT 0.00,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
---
|
||||
读取./sql/08_trade.sql,完善trade_info_view视图逻辑,加入trade_profit信息。
|
||||
---
|
||||
1、看一下./sql/07_variety.sql的内容,并记住这些内容。
|
||||
2、打开./src/logic4variety/create.go,并维持风格不变的前提下,将业务调整为1中的信息。
|
||||
---
|
||||
1、看一下./sql/07_variety.sql的内容,并记住这些内容。
|
||||
2、打开./src/logic4variety/delete.go,并维持风格不变的前提下,将业务调整为1中的信息。
|
||||
---
|
||||
1、看一下和了解./sql/07_variety.sql的内容, 然后帮忙再视图中加入exchange_name的信息。
|
||||
---
|
||||
1、看一下./sql/07_variety.sql的内容,并记住这些内容。
|
||||
2、打开./src/logic4variety/read.go,并维持风格不变的前提下,将业务调整为1中的信息。
|
||||
---
|
||||
1、看一下./sql/07_variety.sql的内容,并记住这些内容。
|
||||
2、打开./src/logic4variety/update.go,并维持风格不变的前提下,将业务调整为1中的信息。
|
||||
---
|
||||
20
backend/sql/02_create_function.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
CREATE OR REPLACE FUNCTION update_data_modified_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE;
|
||||
|
||||
-- 创建自动格式化小数的函数(按需去除尾部多余0)
|
||||
CREATE OR REPLACE FUNCTION format_numeric_to_original(n NUMERIC)
|
||||
RETURNS TEXT AS $$
|
||||
BEGIN
|
||||
-- 逻辑:如果是整数(小数部分全0),返回整数文本;否则返回去除尾部0的文本
|
||||
IF n = TRUNC(n) THEN
|
||||
RETURN TRUNC(n)::TEXT; -- 整数场景:1.000000 → '1'
|
||||
ELSE
|
||||
RETURN TRIM(TRAILING '0' FROM TRIM(TRAILING '.' FROM n::TEXT)); -- 小数场景:1.230000 → '1.23',1.002000 → '1.002'
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql IMMUTABLE; -- IMMUTABLE:相同输入返回相同输出,支持索引
|
||||
116
backend/sql/03_user.sql
Normal file
@@ -0,0 +1,116 @@
|
||||
-- =========================================================
|
||||
-- user.sql 👤
|
||||
-- 无物化视图 | 超可视提示 | 可重复执行
|
||||
-- PostgreSQL 17.4+ 👍
|
||||
-- =========================================================
|
||||
|
||||
-- 1️⃣ 开始 🚀
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🚀============ user 部署开始 ============🚀';
|
||||
END $$;
|
||||
|
||||
-- 2️⃣ 连接目标库
|
||||
\c postgres;
|
||||
|
||||
-- 3️⃣ 表结构 -----------------------------------
|
||||
DO $$
|
||||
BEGIN
|
||||
-- user 主表(关键字,双引号包裹)
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user') THEN
|
||||
CREATE TABLE "user" (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_user_updated_at
|
||||
BEFORE UPDATE ON "user"
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '1️⃣✅ user 主表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '1️⃣⏩ user 主表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- user_account
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user_account') THEN
|
||||
CREATE TABLE user_account (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
user_id UUID NOT NULL,
|
||||
account VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_user_account_updated_at
|
||||
BEFORE UPDATE ON user_account
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '2️⃣✅ user_account 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '2️⃣⏩ user_account 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- user_password
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user_password') THEN
|
||||
CREATE TABLE user_password (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
user_id UUID NOT NULL,
|
||||
password VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_user_password_updated_at
|
||||
BEFORE UPDATE ON user_password
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '3️⃣✅ user_password 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '3️⃣⏩ user_password 子表已存在,跳过';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 4️⃣ 视图 ------------------------------------
|
||||
DO $$
|
||||
DECLARE
|
||||
view_exists BOOLEAN;
|
||||
BEGIN
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.views
|
||||
WHERE table_name = 'user_info_view'
|
||||
) INTO view_exists;
|
||||
|
||||
IF view_exists THEN
|
||||
DROP VIEW user_info_view;
|
||||
RAISE NOTICE '4️⃣♻️ 已删除旧视图 user_info_view';
|
||||
END IF;
|
||||
|
||||
CREATE OR REPLACE VIEW user_info_view AS
|
||||
SELECT
|
||||
u.id AS user_id,
|
||||
ua.account,
|
||||
up.password
|
||||
FROM "user" u
|
||||
JOIN user_account ua ON u.id = ua.user_id AND ua.deleted = FALSE
|
||||
JOIN user_password up ON u.id = up.user_id AND up.deleted = FALSE
|
||||
WHERE u.deleted = FALSE;
|
||||
|
||||
RAISE NOTICE '4️⃣✅ user_info_view 已创建/更新';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE '4️⃣❌ 处理视图时发生错误: %', SQLERRM;
|
||||
END $$;
|
||||
|
||||
-- 5️⃣ 性能索引 ------------------------------------------------
|
||||
CREATE INDEX IF NOT EXISTS idx_user_account_user_id_deleted ON user_account(user_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_password_user_id_deleted ON user_password(user_id, deleted);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '5️⃣✅ 全部索引已确保存在';
|
||||
END $$;
|
||||
|
||||
-- 6️⃣ 完成 🎉
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🎉============ user 部署完成 ============🎉';
|
||||
END $$;
|
||||
137
backend/sql/04_country.sql
Normal file
@@ -0,0 +1,137 @@
|
||||
-- =========================================================
|
||||
-- country.sql 🌍
|
||||
-- 无物化视图 | 超可视提示 | 可重复执行
|
||||
-- PostgreSQL 17.4+ 👍
|
||||
-- =========================================================
|
||||
|
||||
-- 1️⃣ 开始 🚀
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🚀============ country 部署开始 ============🚀';
|
||||
END $$;
|
||||
|
||||
-- 2️⃣ 连接目标库
|
||||
\c postgres;
|
||||
|
||||
-- 3️⃣ 表结构 -----------------------------------
|
||||
DO $$
|
||||
BEGIN
|
||||
-- country 主表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'country') THEN
|
||||
CREATE TABLE country (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_country_updated_at
|
||||
BEFORE UPDATE ON country
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '1️⃣✅ country 主表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '1️⃣⏩ country 主表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- country_name
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'country_name') THEN
|
||||
CREATE TABLE country_name (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
country_id UUID NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_country_name_updated_at
|
||||
BEFORE UPDATE ON country_name
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '2️⃣✅ country_name 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '2️⃣⏩ country_name 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- country_code
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'country_code') THEN
|
||||
CREATE TABLE country_code (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
country_id UUID NOT NULL,
|
||||
code VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_country_code_updated_at
|
||||
BEFORE UPDATE ON country_code
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '3️⃣✅ country_code 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '3️⃣⏩ country_code 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- country_flag
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'country_flag') THEN
|
||||
CREATE TABLE country_flag (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
country_id UUID NOT NULL,
|
||||
flag VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_country_flag_updated_at
|
||||
BEFORE UPDATE ON country_flag
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '4️⃣✅ country_flag 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '4️⃣⏩ country_flag 子表已存在,跳过';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 4️⃣ 视图 ------------------------------------
|
||||
DO $$
|
||||
DECLARE
|
||||
view_exists BOOLEAN;
|
||||
BEGIN
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.views
|
||||
WHERE table_name = 'country_info_view'
|
||||
) INTO view_exists;
|
||||
|
||||
IF view_exists THEN
|
||||
DROP VIEW country_info_view;
|
||||
RAISE NOTICE '4️⃣♻️ 已删除旧视图 country_info_view';
|
||||
END IF;
|
||||
|
||||
CREATE OR REPLACE VIEW country_info_view AS
|
||||
SELECT
|
||||
u.id AS country_id,
|
||||
n.name,
|
||||
c.code,
|
||||
f.flag
|
||||
FROM country u
|
||||
LEFT JOIN country_name n ON u.id = n.country_id AND n.deleted = FALSE
|
||||
LEFT JOIN country_code c ON u.id = c.country_id AND c.deleted = FALSE
|
||||
LEFT JOIN country_flag f ON u.id = f.country_id AND f.deleted = FALSE
|
||||
WHERE u.deleted = FALSE;
|
||||
|
||||
RAISE NOTICE '4️⃣✅ country_info_view 已创建/更新';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE '4️⃣❌ 处理视图时发生错误: %', SQLERRM;
|
||||
END $$;
|
||||
|
||||
-- 5️⃣ 性能索引 ------------------------------------------------
|
||||
CREATE INDEX IF NOT EXISTS idx_country_name_country_id_deleted ON country_name(country_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_country_code_country_id_deleted ON country_code(country_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_country_flag_country_id_deleted ON country_flag(country_id, deleted);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '5️⃣✅ 全部索引已确保存在';
|
||||
END $$;
|
||||
|
||||
-- 6️⃣ 完成 🎉
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🎉============ country 部署完成 ============🎉';
|
||||
END $$;
|
||||
138
backend/sql/05_exchange.sql
Normal file
@@ -0,0 +1,138 @@
|
||||
-- =========================================================
|
||||
-- exchange.sql 🏛️
|
||||
-- 无物化视图 | 超可视提示 | 可重复执行
|
||||
-- PostgreSQL 17.4+ 👍
|
||||
-- =========================================================
|
||||
|
||||
-- 1️⃣ 开始 🚀
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🚀============ exchange 部署开始 ============🚀';
|
||||
END $$;
|
||||
|
||||
-- 2️⃣ 连接目标库
|
||||
\c postgres;
|
||||
|
||||
-- 3️⃣ 表结构 -----------------------------------
|
||||
DO $$
|
||||
BEGIN
|
||||
-- exchange 主表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'exchange') THEN
|
||||
CREATE TABLE exchange (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_exchange_updated_at
|
||||
BEFORE UPDATE ON exchange
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '1️⃣✅ exchange 主表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '1️⃣⏩ exchange 主表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- exchange_name
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'exchange_name') THEN
|
||||
CREATE TABLE exchange_name (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
exchange_id UUID NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_exchange_name_updated_at
|
||||
BEFORE UPDATE ON exchange_name
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '2️⃣✅ exchange_name 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '2️⃣⏩ exchange_name 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- exchange_short_name
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'exchange_short_name') THEN
|
||||
CREATE TABLE exchange_short_name (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
exchange_id UUID NOT NULL,
|
||||
short_name VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_exchange_short_name_updated_at
|
||||
BEFORE UPDATE ON exchange_short_name
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '3️⃣✅ exchange_short_name 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '3️⃣⏩ exchange_short_name 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- exchange_code
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'exchange_code') THEN
|
||||
CREATE TABLE exchange_code (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
exchange_id UUID NOT NULL,
|
||||
code VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_exchange_code_updated_at
|
||||
BEFORE UPDATE ON exchange_code
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '4️⃣✅ exchange_code 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '4️⃣⏩ exchange_code 子表已存在,跳过';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 4️⃣ 视图 ------------------------------------
|
||||
DO $$
|
||||
DECLARE
|
||||
view_exists BOOLEAN;
|
||||
BEGIN
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.views
|
||||
WHERE table_name = 'exchange_info_view'
|
||||
) INTO view_exists;
|
||||
|
||||
IF view_exists THEN
|
||||
DROP VIEW exchange_info_view;
|
||||
RAISE NOTICE '4️⃣♻️ 已删除旧视图 exchange_info_view';
|
||||
END IF;
|
||||
|
||||
CREATE OR REPLACE VIEW exchange_info_view AS
|
||||
SELECT
|
||||
u.id AS exchange_id,
|
||||
n.name,
|
||||
sn.short_name,
|
||||
c.code,
|
||||
u.deleted
|
||||
FROM exchange u
|
||||
JOIN exchange_name n ON u.id = n.exchange_id AND n.deleted = FALSE
|
||||
JOIN exchange_short_name sn ON u.id = sn.exchange_id AND sn.deleted = FALSE
|
||||
JOIN exchange_code c ON u.id = c.exchange_id AND c.deleted = FALSE
|
||||
WHERE u.deleted = FALSE;
|
||||
|
||||
RAISE NOTICE '4️⃣✅ exchange_info_view 已创建/更新';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE '4️⃣❌ 处理视图时发生错误: %', SQLERRM;
|
||||
END $$;
|
||||
|
||||
-- 5️⃣ 性能索引 ------------------------------------------------
|
||||
CREATE INDEX IF NOT EXISTS idx_exchange_name_exchange_id_deleted ON exchange_name(exchange_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_exchange_short_name_exchange_id_deleted ON exchange_short_name(exchange_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_exchange_code_exchange_id_deleted ON exchange_code(exchange_id, deleted);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '5️⃣✅ 全部索引已确保存在';
|
||||
END $$;
|
||||
|
||||
-- 6️⃣ 完成 🎉
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🎉============ exchange 部署完成 ============🎉';
|
||||
END $$;
|
||||
117
backend/sql/06_currency.sql
Normal file
@@ -0,0 +1,117 @@
|
||||
-- =========================================================
|
||||
-- currency.sql 💰
|
||||
-- 无物化视图 | 超可视提示 | 可重复执行
|
||||
-- PostgreSQL 17.4+ 👍
|
||||
-- =========================================================
|
||||
|
||||
-- 1️⃣ 开始 🚀
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🚀============ currency 部署开始 ============🚀';
|
||||
END $$;
|
||||
|
||||
-- 2️⃣ 连接目标库
|
||||
\c postgres;
|
||||
|
||||
-- 3️⃣ 表结构 -----------------------------------
|
||||
DO $$
|
||||
BEGIN
|
||||
-- currency 主表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'currency') THEN
|
||||
CREATE TABLE currency (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_currency_updated_at
|
||||
BEFORE UPDATE ON currency
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '1️⃣✅ currency 主表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '1️⃣⏩ currency 主表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- currency_name
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'currency_name') THEN
|
||||
CREATE TABLE currency_name (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
currency_id UUID NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_currency_name_updated_at
|
||||
BEFORE UPDATE ON currency_name
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '2️⃣✅ currency_name 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '2️⃣⏩ currency_name 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- currency_code
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'currency_code') THEN
|
||||
CREATE TABLE currency_code (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
currency_id UUID NOT NULL,
|
||||
code VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_currency_code_updated_at
|
||||
BEFORE UPDATE ON currency_code
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '3️⃣✅ currency_code 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '3️⃣⏩ currency_code 子表已存在,跳过';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 4️⃣ 视图 ------------------------------------
|
||||
DO $$
|
||||
DECLARE
|
||||
view_exists BOOLEAN;
|
||||
BEGIN
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.views
|
||||
WHERE table_name = 'currency_info_view'
|
||||
) INTO view_exists;
|
||||
|
||||
IF view_exists THEN
|
||||
DROP VIEW currency_info_view;
|
||||
RAISE NOTICE '4️⃣♻️ 已删除旧视图 currency_info_view';
|
||||
END IF;
|
||||
|
||||
CREATE OR REPLACE VIEW currency_info_view AS
|
||||
SELECT
|
||||
u.id AS currency_id,
|
||||
n.name,
|
||||
c.code,
|
||||
u.deleted
|
||||
FROM currency u
|
||||
JOIN currency_name n ON u.id = n.currency_id AND n.deleted = FALSE
|
||||
JOIN currency_code c ON u.id = c.currency_id AND c.deleted = FALSE
|
||||
WHERE u.deleted = FALSE;
|
||||
|
||||
RAISE NOTICE '4️⃣✅ currency_info_view 已创建/更新';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE '4️⃣❌ 处理视图时发生错误: %', SQLERRM;
|
||||
END $$;
|
||||
|
||||
-- 5️⃣ 性能索引 ------------------------------------------------
|
||||
CREATE INDEX IF NOT EXISTS idx_currency_name_currency_id_deleted ON currency_name(currency_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_currency_code_currency_id_deleted ON currency_code(currency_id, deleted);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '5️⃣✅ 全部索引已确保存在';
|
||||
END $$;
|
||||
|
||||
-- 6️⃣ 完成 🎉
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🎉============ currency 部署完成 ============🎉';
|
||||
END $$;
|
||||
165
backend/sql/07_variety.sql
Normal file
@@ -0,0 +1,165 @@
|
||||
-- =========================================================
|
||||
-- variety.sql 🎉
|
||||
-- 无物化视图 | 超可视提示 | 可重复执行
|
||||
-- PostgreSQL 17.4+ 👍
|
||||
-- =========================================================
|
||||
|
||||
-- 1️⃣ 开始 🚀
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🚀============ variety 部署开始 ============🚀';
|
||||
END $$;
|
||||
|
||||
-- 2️⃣ 连接目标库
|
||||
\c postgres;
|
||||
|
||||
-- 3️⃣ 表结构 -----------------------------------
|
||||
DO $$
|
||||
BEGIN
|
||||
-- variety 主表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'variety') THEN
|
||||
CREATE TABLE variety (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
RAISE NOTICE '1️⃣✅ variety 主表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '1️⃣⏩ variety 主表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- variety_exchange
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'variety_exchange') THEN
|
||||
CREATE TABLE variety_exchange (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
variety_id UUID NOT NULL,
|
||||
exchange_id VARCHAR(50) NOT NULL,
|
||||
exchange_name VARCHAR(50) NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_variety_exchange_updated_at
|
||||
BEFORE UPDATE ON variety_exchange
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '2️⃣✅ variety_exchange 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '2️⃣⏩ variety_exchange 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- variety_name
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'variety_name') THEN
|
||||
CREATE TABLE variety_name (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
variety_id UUID NOT NULL,
|
||||
name VARCHAR(50) NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_variety_name_updated_at
|
||||
BEFORE UPDATE ON variety_name
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '3️⃣✅ variety_name 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '3️⃣⏩ variety_name 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- variety_code
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'variety_code') THEN
|
||||
CREATE TABLE variety_code (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
variety_id UUID NOT NULL,
|
||||
code VARCHAR(50) NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_variety_code_updated_at
|
||||
BEFORE UPDATE ON variety_code
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '4️⃣✅ variety_code 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '4️⃣⏩ variety_code 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- variety_tick
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'variety_tick') THEN
|
||||
CREATE TABLE variety_tick (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
variety_id UUID NOT NULL,
|
||||
tick NUMERIC(12,6),
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_variety_tick_updated_at
|
||||
BEFORE UPDATE ON variety_tick
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '5️⃣✅ variety_tick 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '5️⃣⏩ variety_tick 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- variety_tick_price
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'variety_tick_price') THEN
|
||||
CREATE TABLE variety_tick_price (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
variety_id UUID NOT NULL,
|
||||
price NUMERIC(12,6),
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_variety_tick_price_updated_at
|
||||
BEFORE UPDATE ON variety_tick_price
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '6️⃣✅ variety_tick_price 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '6️⃣⏩ variety_tick_price 子表已存在,跳过';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 4️⃣ 视图 ------------------------------------
|
||||
DROP VIEW IF EXISTS variety_info_view;
|
||||
CREATE OR REPLACE VIEW variety_info_view AS
|
||||
SELECT
|
||||
v.id AS variety_id,
|
||||
vn.name,
|
||||
vc.code,
|
||||
ve.exchange_name,
|
||||
format_numeric_to_original(vt.tick) AS tick,
|
||||
format_numeric_to_original(vtp.price) AS tick_price,
|
||||
vt.tick AS tick_original,
|
||||
vtp.price AS tick_price_original
|
||||
FROM variety v
|
||||
LEFT JOIN variety_name vn ON v.id = vn.variety_id AND vn.deleted = FALSE
|
||||
LEFT JOIN variety_code vc ON v.id = vc.variety_id AND vc.deleted = FALSE
|
||||
LEFT JOIN variety_exchange ve ON v.id = ve.variety_id AND ve.deleted = FALSE
|
||||
LEFT JOIN variety_tick vt ON v.id = vt.variety_id AND vt.deleted = FALSE
|
||||
LEFT JOIN variety_tick_price vtp ON v.id = vtp.variety_id AND vtp.deleted = FALSE
|
||||
WHERE v.deleted = FALSE;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '7️⃣✅ variety_info_view 已创建/更新';
|
||||
END $$;
|
||||
|
||||
-- 5️⃣ 性能索引 ------------------------------------------------
|
||||
CREATE INDEX IF NOT EXISTS idx_variety_exchange_variety_id_deleted ON variety_exchange(variety_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_variety_name_variety_id_deleted ON variety_name(variety_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_variety_code_variety_id_deleted ON variety_code(variety_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_variety_tick_variety_id_deleted ON variety_tick(variety_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_variety_tick_price_variety_id_deleted ON variety_tick_price(variety_id, deleted);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '8️⃣✅ 全部索引已确保存在';
|
||||
END $$;
|
||||
|
||||
-- 6️⃣ 完成 🎉
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🎉============ variety 部署完成 ============🎉';
|
||||
END $$;
|
||||
286
backend/sql/08_trade.sql
Normal file
@@ -0,0 +1,286 @@
|
||||
-- =========================================================
|
||||
-- trade.sql 🎉
|
||||
-- 无物化视图 | 超可视提示 | 可重复执行
|
||||
-- PostgreSQL 17.4+ 👍
|
||||
-- =========================================================
|
||||
|
||||
-- 1️⃣ 开始 🚀
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🚀============ trade 部署开始 ============🚀';
|
||||
END $$;
|
||||
|
||||
-- 2️⃣ 连接目标库
|
||||
\c postgres;
|
||||
|
||||
-- 3️⃣ 表结构 -----------------------------------
|
||||
DO $$
|
||||
BEGIN
|
||||
-- trade 主表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'trade') THEN
|
||||
CREATE TABLE trade (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
RAISE NOTICE '1️⃣✅ trade 主表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '1️⃣⏩ trade 主表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- remark 子表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'trade_remark') THEN
|
||||
CREATE TABLE trade_remark (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL REFERENCES trade(id) ON DELETE CASCADE,
|
||||
remark VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_trade_remark_updated_at
|
||||
BEFORE UPDATE ON trade_remark
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '2️⃣✅ trade_remark 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '2️⃣⏩ trade_remark 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- open_date 子表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'trade_open_date') THEN
|
||||
CREATE TABLE trade_open_date (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL REFERENCES trade(id) ON DELETE CASCADE,
|
||||
year INT NOT NULL DEFAULT 0,
|
||||
month INT NOT NULL DEFAULT 0,
|
||||
day INT NOT NULL DEFAULT 0,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_trade_open_date_updated_at
|
||||
BEFORE UPDATE ON trade_open_date
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '3️⃣✅ trade_open_date 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '3️⃣⏩ trade_open_date 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- variety 子表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'trade_variety') THEN
|
||||
CREATE TABLE trade_variety (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL REFERENCES trade(id) ON DELETE CASCADE,
|
||||
variety_id UUID NOT NULL,
|
||||
variety_name VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_trade_variety_updated_at
|
||||
BEFORE UPDATE ON trade_variety
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '4️⃣✅ trade_variety 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '4️⃣⏩ trade_variety 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- direction 子表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'trade_direction') THEN
|
||||
CREATE TABLE trade_direction (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL REFERENCES trade(id) ON DELETE CASCADE,
|
||||
direction VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_trade_direction_updated_at
|
||||
BEFORE UPDATE ON trade_direction
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '5️⃣✅ trade_direction 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '5️⃣⏩ trade_direction 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- open_price 子表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'trade_open_price') THEN
|
||||
CREATE TABLE trade_open_price (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL REFERENCES trade(id) ON DELETE CASCADE,
|
||||
open_price NUMERIC(10,6) NOT NULL CHECK (open_price >= 0.00) DEFAULT 0.00,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_trade_open_price_updated_at
|
||||
BEFORE UPDATE ON trade_open_price
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '6️⃣✅ trade_open_price 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '6️⃣⏩ trade_open_price 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- open_fee 子表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'trade_open_fee') THEN
|
||||
CREATE TABLE trade_open_fee (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL REFERENCES trade(id) ON DELETE CASCADE,
|
||||
open_fee NUMERIC(10,6) NOT NULL CHECK (open_fee >= 0.00) DEFAULT 0.00,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_trade_open_fee_updated_at
|
||||
BEFORE UPDATE ON trade_open_fee
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '7️⃣✅ trade_open_fee 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '7️⃣⏩ trade_open_fee 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- close_date 子表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'trade_close_date') THEN
|
||||
CREATE TABLE trade_close_date (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL REFERENCES trade(id) ON DELETE CASCADE,
|
||||
year INT NOT NULL DEFAULT 0,
|
||||
month INT NOT NULL DEFAULT 0,
|
||||
day INT NOT NULL DEFAULT 0,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_trade_close_date_updated_at
|
||||
BEFORE UPDATE ON trade_close_date
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '8️⃣✅ trade_close_date 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '8️⃣⏩ trade_close_date 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- close_price 子表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'trade_close_price') THEN
|
||||
CREATE TABLE trade_close_price (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL REFERENCES trade(id) ON DELETE CASCADE,
|
||||
close_price NUMERIC(10,6) NOT NULL CHECK (close_price >= 0.00) DEFAULT 0.00,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_trade_close_price_updated_at
|
||||
BEFORE UPDATE ON trade_close_price
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '9️⃣✅ trade_close_price 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '9️⃣⏩ trade_close_price 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- close_fee 子表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'trade_close_fee') THEN
|
||||
CREATE TABLE trade_close_fee (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL REFERENCES trade(id) ON DELETE CASCADE,
|
||||
close_fee NUMERIC(10,6) NOT NULL CHECK (close_fee >= 0.00) DEFAULT 0.00,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_trade_close_fee_updated_at
|
||||
BEFORE UPDATE ON trade_close_fee
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '🔟✅ trade_close_fee 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '🔟⏩ trade_close_fee 子表已存在,跳过';
|
||||
END IF;
|
||||
|
||||
-- profit 子表
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'trade_profit') THEN
|
||||
CREATE TABLE trade_profit (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
trade_id UUID NOT NULL REFERENCES trade(id) ON DELETE CASCADE,
|
||||
variety_tick NUMERIC(12,6) NOT NULL DEFAULT 0.00,
|
||||
variety_tick_price NUMERIC(12,6) NOT NULL CHECK (variety_tick_price >= 0.00) DEFAULT 0.00,
|
||||
win_tick NUMERIC(12,6) NOT NULL DEFAULT 0.00,
|
||||
win_tick_price NUMERIC(12,6) NOT NULL DEFAULT 0.00,
|
||||
fee_cost NUMERIC(12,6) NOT NULL DEFAULT 0.00,
|
||||
trade_win NUMERIC(12,6) NOT NULL DEFAULT 0.00,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_trade_profit_updated_at
|
||||
BEFORE UPDATE ON trade_profit
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '1️⃣1️⃣✅ trade_profit 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE '1️⃣1️⃣⏩ trade_profit 子表已存在,跳过';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 4️⃣ 视图 ------------------------------------
|
||||
DROP VIEW IF EXISTS trade_info_view;
|
||||
CREATE OR REPLACE VIEW trade_info_view AS
|
||||
SELECT
|
||||
t.id AS trade_id,
|
||||
r.remark AS remark,
|
||||
od.year AS open_year,
|
||||
od.month AS open_month,
|
||||
od.day AS open_day,
|
||||
v.variety_name AS variety_name,
|
||||
d.direction AS direction,
|
||||
format_numeric_to_original(op.open_price) AS open_price,
|
||||
format_numeric_to_original(of.open_fee) AS open_fee,
|
||||
cd.year AS close_year,
|
||||
cd.month AS close_month,
|
||||
cd.day AS close_day,
|
||||
format_numeric_to_original(cp.close_price) AS close_price,
|
||||
format_numeric_to_original(cf.close_fee) AS close_fee,
|
||||
p.variety_tick AS variety_tick,
|
||||
p.variety_tick_price AS variety_tick_price,
|
||||
p.win_tick AS win_tick,
|
||||
p.win_tick_price AS win_tick_price,
|
||||
format_numeric_to_original(p.fee_cost) AS fee_cost,
|
||||
format_numeric_to_original(p.trade_win) AS trade_win
|
||||
FROM trade t
|
||||
LEFT JOIN trade_remark r ON t.id = r.trade_id AND r.deleted = FALSE
|
||||
LEFT JOIN trade_open_date od ON t.id = od.trade_id AND od.deleted = FALSE
|
||||
LEFT JOIN trade_variety v ON t.id = v.trade_id AND v.deleted = FALSE
|
||||
LEFT JOIN trade_direction d ON t.id = d.trade_id AND d.deleted = FALSE
|
||||
LEFT JOIN trade_open_price op ON t.id = op.trade_id AND op.deleted = FALSE
|
||||
LEFT JOIN trade_open_fee of ON t.id = of.trade_id AND of.deleted = FALSE
|
||||
LEFT JOIN trade_close_date cd ON t.id = cd.trade_id AND cd.deleted = FALSE
|
||||
LEFT JOIN trade_close_price cp ON t.id = cp.trade_id AND cp.deleted = FALSE
|
||||
LEFT JOIN trade_close_fee cf ON t.id = cf.trade_id AND cf.deleted = FALSE
|
||||
LEFT JOIN trade_profit p ON t.id = p.trade_id AND p.deleted = FALSE
|
||||
WHERE t.deleted = FALSE;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '4️⃣✅ trade_info_view 已创建/更新';
|
||||
END $$;
|
||||
|
||||
-- 5️⃣ 性能索引 ------------------------------------------------
|
||||
CREATE INDEX IF NOT EXISTS idx_trade_remark_trade_id_deleted ON trade_remark(trade_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_trade_open_date_trade_id_deleted ON trade_open_date(trade_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_trade_variety_trade_id_deleted ON trade_variety(trade_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_trade_direction_trade_id_deleted ON trade_direction(trade_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_trade_open_price_trade_id_deleted ON trade_open_price(trade_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_trade_open_fee_trade_id_deleted ON trade_open_fee(trade_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_trade_close_date_trade_id_deleted ON trade_close_date(trade_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_trade_close_price_trade_id_deleted ON trade_close_price(trade_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_trade_close_fee_trade_id_deleted ON trade_close_fee(trade_id, deleted);
|
||||
CREATE INDEX IF NOT EXISTS idx_trade_profit_trade_id_deleted ON trade_profit(trade_id, deleted);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '5️⃣✅ 全部索引已确保存在';
|
||||
END $$;
|
||||
|
||||
-- 6️⃣ 完成 🎉
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🎉============ trade 部署完成 ============🎉';
|
||||
END $$;
|
||||
@@ -1,8 +1,9 @@
|
||||
module user
|
||||
module asset_assistant
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/cors v1.7.6
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/lib/pq v1.10.9
|
||||
@@ -17,19 +18,19 @@ require (
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
@@ -11,8 +11,10 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
||||
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
@@ -27,8 +29,8 @@ github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHO
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -50,8 +52,9 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
@@ -121,8 +124,8 @@ golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
303
backend/src/logic4country/create.go
Normal file
@@ -0,0 +1,303 @@
|
||||
package logic4country
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// CreateRequest 注册请求参数结构
|
||||
type CreateRequest struct {
|
||||
Name string `json:"name" binding:"required"` // 国家名称,必填
|
||||
Code string `json:"code" binding:"required"` // 国家代码,必填
|
||||
Flag string `json:"flag"` // 国旗信息,可选
|
||||
}
|
||||
|
||||
// CreateResponse 注册响应结构
|
||||
type CreateResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data CreateData `json:"data"`
|
||||
}
|
||||
|
||||
// CreateData 响应数据结构
|
||||
type CreateData struct {
|
||||
CountryID string `json:"country_id"`
|
||||
}
|
||||
|
||||
// CreateHandler 处理创建逻辑
|
||||
func CreateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-RegisterRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
zap.L().Info("📥 收到国家创建请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req CreateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
zap.Any("request_body", c.Request.Body),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, CreateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:name和code为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
zap.String("flag", req.Flag),
|
||||
)
|
||||
|
||||
// 开启数据库事务
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 唯一性校验 - 国家名称(排除已删除数据)
|
||||
var nameCount int
|
||||
err = tx.QueryRow(
|
||||
"SELECT COUNT(*) FROM country_name WHERE name = $1 AND deleted = false",
|
||||
req.Name,
|
||||
).Scan(&nameCount)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 国家名称唯一性校验失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("name", req.Name),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,校验名称失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
if nameCount > 0 {
|
||||
tx.Rollback()
|
||||
zap.L().Warn("⚠️ 国家名称已存在(未删除数据)",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("name", req.Name),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, CreateResponse{
|
||||
Success: false,
|
||||
Message: "国家名称已存在,请更换名称",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 唯一性校验 - 国家编码(排除已删除数据)
|
||||
var codeCount int
|
||||
err = tx.QueryRow(
|
||||
"SELECT COUNT(*) FROM country_code WHERE code = $1 AND deleted = false",
|
||||
req.Code,
|
||||
).Scan(&codeCount)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 国家编码唯一性校验失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("code", req.Code),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,校验编码失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
if codeCount > 0 {
|
||||
tx.Rollback()
|
||||
zap.L().Warn("⚠️ 国家编码已存在(未删除数据)",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("code", req.Code),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, CreateResponse{
|
||||
Success: false,
|
||||
Message: "国家编码已存在,请更换编码",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 唯一性校验 - 国旗(排除已删除数据,仅当提供了国旗参数时)
|
||||
if req.Flag != "" {
|
||||
var flagCount int
|
||||
err = tx.QueryRow(
|
||||
"SELECT COUNT(*) FROM country_flag WHERE flag = $1 AND deleted = false",
|
||||
req.Flag,
|
||||
).Scan(&flagCount)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 国旗唯一性校验失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("flag", req.Flag),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,校验国旗失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
if flagCount > 0 {
|
||||
tx.Rollback()
|
||||
zap.L().Warn("⚠️ 国旗信息已存在(未删除数据)",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("flag", req.Flag),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, CreateResponse{
|
||||
Success: false,
|
||||
Message: "国旗信息已存在,请更换国旗",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 创建country主表记录
|
||||
var countryID string
|
||||
err = tx.QueryRow("INSERT INTO country DEFAULT VALUES RETURNING id").Scan(&countryID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ country表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "创建国家记录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("📝 country表插入成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", countryID),
|
||||
)
|
||||
|
||||
// 2. 插入国家名称
|
||||
_, err = tx.Exec("INSERT INTO country_name (country_id, name) VALUES ($1, $2)", countryID, req.Name)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ country_name表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", countryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 插入国家代码
|
||||
_, err = tx.Exec("INSERT INTO country_code (country_id, code) VALUES ($1, $2)", countryID, req.Code)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ country_code表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", countryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存代码信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 插入国旗信息(如果提供)
|
||||
if req.Flag != "" {
|
||||
_, err = tx.Exec("INSERT INTO country_flag (country_id, flag) VALUES ($1, $2)", countryID, req.Flag)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ country_flag表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", countryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存国旗信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 country_flag表插入成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", countryID),
|
||||
)
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", countryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 国家创建请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", countryID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
c.JSON(http.StatusOK, CreateResponse{
|
||||
Success: true,
|
||||
Message: "创建成功",
|
||||
Data: CreateData{
|
||||
CountryID: countryID,
|
||||
},
|
||||
})
|
||||
}
|
||||
188
backend/src/logic4country/delete.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package logic4country
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// DeleteRequest 删除请求参数结构
|
||||
type DeleteRequest struct {
|
||||
CountryID string `json:"country_id" binding:"required"` // 国家ID,必填
|
||||
}
|
||||
|
||||
// DeleteResponse 删除响应结构
|
||||
type DeleteResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// DeleteHandler 处理删除逻辑(软删除)
|
||||
func DeleteHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-DeleteRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
zap.L().Info("📥 收到国家删除请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req DeleteRequest
|
||||
// 绑定并验证请求参数
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:country_id为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
)
|
||||
|
||||
// 开启数据库事务
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 延迟处理panic情况
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 3.1 更新country表
|
||||
_, err = tx.Exec("UPDATE country SET deleted = TRUE WHERE id = $1", req.CountryID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ country表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除国家记录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3.2 更新name表
|
||||
_, err = tx.Exec("UPDATE country_name SET deleted = TRUE WHERE country_id = $1", req.CountryID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ country_name表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3.3 更新code表
|
||||
_, err = tx.Exec("UPDATE country_code SET deleted = TRUE WHERE country_id = $1", req.CountryID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ country_code表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除代码信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 新增:3.4 更新flag表(软删除国旗信息)
|
||||
_, err = tx.Exec("UPDATE country_flag SET deleted = TRUE WHERE country_id = $1", req.CountryID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ country_flag表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除国旗信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 flag表软删除成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
)
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 国家删除请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, DeleteResponse{
|
||||
Success: true,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
227
backend/src/logic4country/read.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package logic4country
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ReadRequest 读取请求参数结构
|
||||
type ReadRequest struct {
|
||||
CountryID string `form:"country_id"` // 国家ID,可选
|
||||
Name string `form:"name"` // 国家名称,可选
|
||||
Code string `form:"code"` // 国家代码,可选
|
||||
Flag string `form:"flag"` // 国旗信息,新增可选参数
|
||||
Page string `form:"page"` // 页码,可选
|
||||
PageSize string `form:"page_size"` // 每页条数,可选
|
||||
}
|
||||
|
||||
// ReadData 读取响应数据结构
|
||||
type ReadData struct {
|
||||
Total int64 `json:"total"` // 总条数
|
||||
Page int `json:"page"` // 当前页码
|
||||
PageSize int `json:"page_size"` // 每页条数
|
||||
Items []CountryInfoViewItem `json:"items"` // 数据列表
|
||||
}
|
||||
|
||||
// CountryInfoViewItem 视图数据项结构,新增国旗字段
|
||||
type CountryInfoViewItem struct {
|
||||
CountryID string `json:"country_id"` // 国家ID
|
||||
Name string `json:"name"` // 国家名称
|
||||
Code string `json:"code"` // 国家代码
|
||||
Flag string `json:"flag"` // 国旗信息,新增字段
|
||||
}
|
||||
|
||||
// ReadResponse 读取响应结构
|
||||
type ReadResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
Data ReadData `json:"data"` // 响应数据
|
||||
}
|
||||
|
||||
// ReadHandler 处理查询逻辑
|
||||
func ReadHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
reqID := c.Request.Header.Get("X-ReadRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
// 记录请求接收日志
|
||||
zap.L().Info("📥 收到国家查询请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
// 绑定请求参数
|
||||
var req ReadRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数解析失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, ReadResponse{
|
||||
Success: false,
|
||||
Message: "请求参数格式错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 处理分页参数默认值
|
||||
page, err := strconv.Atoi(req.Page)
|
||||
if err != nil || page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize, err := strconv.Atoi(req.PageSize)
|
||||
if err != nil || pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
zap.String("flag", req.Flag), // 新增国旗查询参数日志
|
||||
zap.Int("page", page),
|
||||
zap.Int("page_size", pageSize),
|
||||
)
|
||||
|
||||
// 构建查询条件和参数
|
||||
whereClauses := []string{}
|
||||
args := []interface{}{}
|
||||
paramIndex := 1
|
||||
|
||||
if req.CountryID != "" {
|
||||
whereClauses = append(whereClauses, "country_id = $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, req.CountryID)
|
||||
paramIndex++
|
||||
}
|
||||
if req.Name != "" {
|
||||
whereClauses = append(whereClauses, "name LIKE $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, "%"+req.Name+"%")
|
||||
paramIndex++
|
||||
}
|
||||
if req.Code != "" {
|
||||
whereClauses = append(whereClauses, "code LIKE $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, "%"+req.Code+"%")
|
||||
paramIndex++
|
||||
}
|
||||
// 新增国旗查询条件
|
||||
if req.Flag != "" {
|
||||
whereClauses = append(whereClauses, "flag LIKE $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, "%"+req.Flag+"%")
|
||||
paramIndex++
|
||||
}
|
||||
|
||||
// 构建基础SQL,新增flag字段查询
|
||||
baseSQL := "SELECT country_id, name, code, flag FROM country_info_view"
|
||||
countSQL := "SELECT COUNT(*) FROM country_info_view"
|
||||
if len(whereClauses) > 0 {
|
||||
whereStr := " WHERE " + strings.Join(whereClauses, " AND ")
|
||||
baseSQL += whereStr
|
||||
countSQL += whereStr
|
||||
}
|
||||
|
||||
// 计算分页偏移量
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
// 拼接分页SQL
|
||||
querySQL := fmt.Sprintf("%s ORDER BY country_id LIMIT $%d OFFSET $%d", baseSQL, paramIndex, paramIndex+1)
|
||||
args = append(args, pageSize, offset)
|
||||
|
||||
// 查询总条数
|
||||
var total int64
|
||||
countArgs := args[:len(args)-2] // 排除分页参数
|
||||
err = db.DB.QueryRow(countSQL, countArgs...).Scan(&total)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 查询总条数失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 执行分页查询
|
||||
rows, err := db.DB.Query(querySQL, args...)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 分页查询失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// 处理查询结果,新增flag字段扫描
|
||||
var items []CountryInfoViewItem
|
||||
for rows.Next() {
|
||||
var item CountryInfoViewItem
|
||||
if err := rows.Scan(&item.CountryID, &item.Name, &item.Code, &item.Flag); err != nil {
|
||||
zap.L().Error("❌ 解析查询结果失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "数据处理失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
// 检查行迭代过程中是否发生错误
|
||||
if err := rows.Err(); err != nil {
|
||||
zap.L().Error("❌ 行迭代错误",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 国家查询请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Int64("total", total),
|
||||
zap.Int("page", page),
|
||||
zap.Int("page_size", pageSize),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, ReadResponse{
|
||||
Success: true,
|
||||
Message: "查询成功",
|
||||
Data: ReadData{
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Items: items,
|
||||
},
|
||||
})
|
||||
}
|
||||
232
backend/src/logic4country/update.go
Normal file
@@ -0,0 +1,232 @@
|
||||
package logic4country
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// UpdateRequest 更新请求参数结构
|
||||
type UpdateRequest struct {
|
||||
CountryID string `json:"country_id" binding:"required"` // 国家ID,必填
|
||||
Name string `json:"name"` // 国家名称,可选
|
||||
Code string `json:"code"` // 国家代码,可选
|
||||
Flag string `json:"flag"` // 国旗信息,可选(新增字段)
|
||||
}
|
||||
|
||||
// UpdateResponse 更新响应结构
|
||||
type UpdateResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// UpdateHandler 更新逻辑
|
||||
func UpdateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
reqID := c.Request.Header.Get("X-UpdateRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
// 记录请求接收日志
|
||||
zap.L().Info("📥 收到国家更新请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req UpdateRequest
|
||||
// 绑定并验证请求参数(主要验证country_id必填)
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:country_id为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证name、code和flag不能同时为空
|
||||
if req.Name == "" && req.Code == "" && req.Flag == "" {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.String("reason", "name、code和flag不能同时为空"),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:name、code和flag不能同时为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
zap.String("flag", req.Flag), // 新增国旗参数日志
|
||||
)
|
||||
|
||||
// 开启数据库事务
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 延迟处理panic情况
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 如果name不为空,更新name表
|
||||
if req.Name != "" {
|
||||
_, err = tx.Exec("UPDATE country_name SET name = $1, updated_at = CURRENT_TIMESTAMP WHERE country_id = $2", req.Name, req.CountryID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ country_name表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 name表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
)
|
||||
}
|
||||
|
||||
// 如果code不为空,更新code表
|
||||
if req.Code != "" {
|
||||
_, err = tx.Exec("UPDATE country_code SET code = $1, updated_at = CURRENT_TIMESTAMP WHERE country_id = $2", req.Code, req.CountryID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ country_code表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新代码信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 code表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
)
|
||||
}
|
||||
|
||||
// 新增:如果flag不为空,更新flag表
|
||||
if req.Flag != "" {
|
||||
// 先检查是否存在国旗记录
|
||||
var flagExists bool
|
||||
err = tx.QueryRow("SELECT EXISTS(SELECT 1 FROM country_flag WHERE country_id = $1 AND deleted = FALSE)", req.CountryID).Scan(&flagExists)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 检查国旗记录存在性失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "检查国旗信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if flagExists {
|
||||
// 存在则更新
|
||||
_, err = tx.Exec("UPDATE country_flag SET flag = $1, updated_at = CURRENT_TIMESTAMP WHERE country_id = $2", req.Flag, req.CountryID)
|
||||
} else {
|
||||
// 不存在则插入新记录
|
||||
_, err = tx.Exec("INSERT INTO country_flag (country_id, flag) VALUES ($1, $2)", req.CountryID, req.Flag)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ country_flag表更新/插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新国旗信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 flag表更新/插入成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
)
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 国家更新请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("country_id", req.CountryID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, UpdateResponse{
|
||||
Success: true,
|
||||
Message: "更新成功",
|
||||
})
|
||||
}
|
||||
193
backend/src/logic4currency/create.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package logic4currency
|
||||
|
||||
import (
|
||||
"asset_assistant/db" // 数据库操作相关包
|
||||
"net/http"
|
||||
"time" // 时间处理包
|
||||
|
||||
"github.com/gin-gonic/gin" // Gin框架,用于处理HTTP请求
|
||||
"github.com/google/uuid" // UUID生成工具
|
||||
"go.uber.org/zap" // 日志库
|
||||
)
|
||||
|
||||
// CreateRequest 注册请求参数结构
|
||||
// 用于接收客户端发送的JSON数据,绑定并验证必填字段
|
||||
type CreateRequest struct {
|
||||
Name string `json:"name" binding:"required"` // 货币名称,必填
|
||||
Code string `json:"code" binding:"required"` // 货币代码,必填
|
||||
}
|
||||
|
||||
// CreateResponse 注册响应结构
|
||||
// 统一的API响应格式,包含成功状态、提示信息和数据
|
||||
type CreateResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
Data CreateData `json:"data"` // 响应数据
|
||||
}
|
||||
|
||||
// CreateData 响应数据结构
|
||||
// 包含创建成功后的货币ID
|
||||
type CreateData struct {
|
||||
CurrencyID string `json:"currency_id"` // 货币唯一标识ID
|
||||
}
|
||||
|
||||
// CreateHandler 处理创建逻辑
|
||||
func CreateHandler(c *gin.Context) {
|
||||
startTime := time.Now() // 记录请求开始时间,用于统计耗时
|
||||
// 获取或生成请求ID,用于追踪整个请求链路
|
||||
reqID := c.Request.Header.Get("X-RegisterRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
// 记录请求接收日志,包含关键追踪信息
|
||||
zap.L().Info("📥 收到货币创建请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req CreateRequest
|
||||
// 绑定并验证请求参数(检查name和code是否存在)
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
zap.Any("request_body", c.Request.Body),
|
||||
)
|
||||
// 返回参数错误响应
|
||||
c.JSON(http.StatusBadRequest, CreateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:name和code为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录通过验证的请求参数
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
)
|
||||
|
||||
// 开启数据库事务,确保多表操作原子性(要么全成功,要么全失败)
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 延迟执行的恢复函数,处理panic情况
|
||||
defer func() {
|
||||
if r := recover(); r != nil { // 捕获panic
|
||||
// 回滚事务
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
// 返回系统错误响应
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 1. 在currency表中创建记录并获取自动生成的ID
|
||||
var currencyID string
|
||||
err = tx.QueryRow("INSERT INTO currency DEFAULT VALUES RETURNING id").Scan(¤cyID)
|
||||
if err != nil {
|
||||
tx.Rollback() // 操作失败,回滚事务
|
||||
zap.L().Error("❌ currency表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "创建货币记录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("📝 currency表插入成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", currencyID),
|
||||
)
|
||||
|
||||
// 2. 插入货币名称到name表(与currency_id关联)
|
||||
_, err = tx.Exec("INSERT INTO currency_name (currency_id, name) VALUES ($1, $2)", currencyID, req.Name)
|
||||
if err != nil {
|
||||
tx.Rollback() // 操作失败,回滚事务
|
||||
zap.L().Error("❌ currency_name表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", currencyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 插入货币代码到code表(与currency_id关联)
|
||||
_, err = tx.Exec("INSERT INTO currency_code (currency_id, code) VALUES ($1, $2)", currencyID, req.Code)
|
||||
if err != nil {
|
||||
tx.Rollback() // 操作失败,回滚事务
|
||||
zap.L().Error("❌ currency_code表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", currencyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存代码信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 提交事务(所有操作成功后确认提交)
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback() // 提交失败时尝试回滚
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", currencyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 货币创建请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", currencyID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应,包含创建的货币ID
|
||||
c.JSON(http.StatusOK, CreateResponse{
|
||||
Success: true,
|
||||
Message: "创建成功",
|
||||
Data: CreateData{
|
||||
CurrencyID: currencyID,
|
||||
},
|
||||
})
|
||||
}
|
||||
168
backend/src/logic4currency/delete.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package logic4currency
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// DeleteRequest 删除请求参数结构
|
||||
type DeleteRequest struct {
|
||||
CurrencyID string `json:"currency_id" binding:"required"` // 货币ID,必填
|
||||
}
|
||||
|
||||
// DeleteResponse 删除响应结构
|
||||
type DeleteResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// DeleteHandler 处理删除逻辑(软删除)
|
||||
func DeleteHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-DeleteRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
zap.L().Info("📥 收到货币删除请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req DeleteRequest
|
||||
// 绑定并验证请求参数
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:currency_id为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
)
|
||||
|
||||
// 开启数据库事务
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 延迟处理panic情况
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 3.1 更新currency表
|
||||
_, err = tx.Exec("UPDATE currency SET deleted = TRUE WHERE id = $1", req.CurrencyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ currency表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除货币记录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3.2 更新name表
|
||||
_, err = tx.Exec("UPDATE currency_name SET deleted = TRUE WHERE currency_id = $1", req.CurrencyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ currency_name表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3.3 更新code表
|
||||
_, err = tx.Exec("UPDATE currency_code SET deleted = TRUE WHERE currency_id = $1", req.CurrencyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ currency_code表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除代码信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 货币删除请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, DeleteResponse{
|
||||
Success: true,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
231
backend/src/logic4currency/read.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package logic4currency
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ReadRequest 读取请求参数结构
|
||||
type ReadRequest struct {
|
||||
CurrencyID string `form:"currency_id"` // 货币ID,可选
|
||||
Name string `form:"name"` // 货币名称,可选
|
||||
Code string `form:"code"` // 货币代码,可选
|
||||
Page string `form:"page"` // 页码,可选
|
||||
PageSize string `form:"page_size"` // 每页条数,可选
|
||||
}
|
||||
|
||||
// ReadData 读取响应数据结构
|
||||
type ReadData struct {
|
||||
Total int64 `json:"total"` // 总条数
|
||||
Page int `json:"page"` // 当前页码
|
||||
PageSize int `json:"page_size"` // 每页条数
|
||||
Items []CurrencyInfoViewItem `json:"items"` // 数据列表
|
||||
}
|
||||
|
||||
// CurrencyInfoViewItem 视图数据项结构
|
||||
type CurrencyInfoViewItem struct {
|
||||
CurrencyID string `json:"currency_id"` // 货币ID
|
||||
Name string `json:"name"` // 货币名称
|
||||
Code string `json:"code"` // 货币代码
|
||||
}
|
||||
|
||||
// ReadResponse 读取响应结构
|
||||
type ReadResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
Data ReadData `json:"data"` // 响应数据
|
||||
}
|
||||
|
||||
// ReadHandler 处理信息查询逻辑
|
||||
func ReadHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
reqID := c.Request.Header.Get("X-ReadRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
// 记录请求接收日志
|
||||
zap.L().Info("📥 收到货币查询请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
// 绑定请求参数
|
||||
var req ReadRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数解析失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, ReadResponse{
|
||||
Success: false,
|
||||
Message: "请求参数格式错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证查询条件至少有一个不为空
|
||||
if req.CurrencyID == "" && req.Name == "" && req.Code == "" {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("reason", "currency_id、name、code不能同时为空"),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, ReadResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:currency_id、name、code不能同时为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 处理分页参数默认值
|
||||
page, err := strconv.Atoi(req.Page)
|
||||
if err != nil || page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize, err := strconv.Atoi(req.PageSize)
|
||||
if err != nil || pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
zap.Int("page", page),
|
||||
zap.Int("page_size", pageSize),
|
||||
)
|
||||
|
||||
// 构建查询条件和参数
|
||||
whereClauses := []string{}
|
||||
args := []interface{}{}
|
||||
paramIndex := 1
|
||||
|
||||
if req.CurrencyID != "" {
|
||||
whereClauses = append(whereClauses, "currency_id = $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, req.CurrencyID)
|
||||
paramIndex++
|
||||
}
|
||||
if req.Name != "" {
|
||||
whereClauses = append(whereClauses, "name LIKE $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, "%"+req.Name+"%")
|
||||
paramIndex++
|
||||
}
|
||||
if req.Code != "" {
|
||||
whereClauses = append(whereClauses, "code LIKE $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, "%"+req.Code+"%")
|
||||
paramIndex++
|
||||
}
|
||||
|
||||
// 构建基础SQL
|
||||
baseSQL := "SELECT currency_id, name, code FROM currency_info_view"
|
||||
countSQL := "SELECT COUNT(*) FROM currency_info_view"
|
||||
if len(whereClauses) > 0 {
|
||||
whereStr := " WHERE " + strings.Join(whereClauses, " AND ")
|
||||
baseSQL += whereStr
|
||||
countSQL += whereStr
|
||||
}
|
||||
|
||||
// 计算分页偏移量
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
// 拼接分页SQL(使用fmt.Sprintf更清晰)
|
||||
querySQL := fmt.Sprintf("%s ORDER BY currency_id LIMIT $%d OFFSET $%d", baseSQL, paramIndex, paramIndex+1)
|
||||
args = append(args, pageSize, offset)
|
||||
|
||||
// 查询总条数(修正参数传递方式)
|
||||
var total int64
|
||||
countArgs := args[:len(args)-2] // 排除分页参数
|
||||
err = db.DB.QueryRow(countSQL, countArgs...).Scan(&total)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 查询总条数失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 执行分页查询
|
||||
rows, err := db.DB.Query(querySQL, args...)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 分页查询失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// 处理查询结果
|
||||
var items []CurrencyInfoViewItem
|
||||
for rows.Next() {
|
||||
var item CurrencyInfoViewItem
|
||||
if err := rows.Scan(&item.CurrencyID, &item.Name, &item.Code); err != nil {
|
||||
zap.L().Error("❌ 解析查询结果失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "数据处理失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
// 检查行迭代过程中是否发生错误
|
||||
if err := rows.Err(); err != nil {
|
||||
zap.L().Error("❌ 行迭代错误",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 货币查询请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Int64("total", total),
|
||||
zap.Int("page", page),
|
||||
zap.Int("page_size", pageSize),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, ReadResponse{
|
||||
Success: true,
|
||||
Message: "查询成功",
|
||||
Data: ReadData{
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Items: items,
|
||||
},
|
||||
})
|
||||
}
|
||||
184
backend/src/logic4currency/update.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package logic4currency
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// UpdateRequest 更新请求参数结构
|
||||
type UpdateRequest struct {
|
||||
CurrencyID string `json:"currency_id" binding:"required"` // 货币ID,必填
|
||||
Name string `json:"name"` // 货币名称,可选
|
||||
Code string `json:"code"` // 货币代码,可选
|
||||
}
|
||||
|
||||
// UpdateResponse 更新响应结构
|
||||
type UpdateResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// UpdateHandler 处理信息更新逻辑
|
||||
func UpdateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
reqID := c.Request.Header.Get("X-UpdateRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
// 记录请求接收日志
|
||||
zap.L().Info("📥 收到货币更新请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req UpdateRequest
|
||||
// 绑定并验证请求参数(主要验证currency_id必填)
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:currency_id为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证name和code不能同时为空
|
||||
if req.Name == "" && req.Code == "" {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.String("reason", "name和code不能同时为空"),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:name和code不能同时为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
)
|
||||
|
||||
// 开启数据库事务
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 延迟处理panic情况
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 如果name不为空,更新name表
|
||||
if req.Name != "" {
|
||||
_, err = tx.Exec("UPDATE currency_name SET name = $1 WHERE currency_id = $2", req.Name, req.CurrencyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ currency_name表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 name表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
)
|
||||
}
|
||||
|
||||
// 如果code不为空,更新code表
|
||||
if req.Code != "" {
|
||||
_, err = tx.Exec("UPDATE currency_code SET code = $1 WHERE currency_id = $2", req.Code, req.CurrencyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ currency_code表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新代码信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 code表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
)
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 货币更新请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("currency_id", req.CurrencyID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, UpdateResponse{
|
||||
Success: true,
|
||||
Message: "更新成功",
|
||||
})
|
||||
}
|
||||
200
backend/src/logic4exchange/create.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package logic4exchange
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// CreateRequest 注册请求参数结构
|
||||
// 新增short_name字段,保持必填性与其他核心字段一致
|
||||
type CreateRequest struct {
|
||||
Name string `json:"name" binding:"required"` // 交易所名称,必填
|
||||
Code string `json:"code" binding:"required"` // 交易所代码,必填
|
||||
ShortName string `json:"short_name" binding:"required"` // 交易所短名称,必填
|
||||
}
|
||||
|
||||
// CreateResponse 注册响应结构
|
||||
type CreateResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data CreateData `json:"data"`
|
||||
}
|
||||
|
||||
// CreateData 响应数据结构
|
||||
type CreateData struct {
|
||||
ExchangeID string `json:"exchange_id"`
|
||||
}
|
||||
|
||||
// CreateHandler 处理创建逻辑
|
||||
func CreateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-RegisterRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
zap.L().Info("📥 收到交易所创建请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req CreateRequest
|
||||
// 绑定参数时会自动验证name、code、short_name三个必填字段
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
zap.Any("request_body", c.Request.Body),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, CreateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:name、code和short_name为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
zap.String("short_name", req.ShortName), // 新增短名称日志
|
||||
)
|
||||
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 1. 创建exchange主记录
|
||||
var exchangeID string
|
||||
err = tx.QueryRow("INSERT INTO exchange DEFAULT VALUES RETURNING id").Scan(&exchangeID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ exchange表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "创建交易所记录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("📝 exchange表插入成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", exchangeID),
|
||||
)
|
||||
|
||||
// 2. 插入名称信息
|
||||
_, err = tx.Exec("INSERT INTO exchange_name (exchange_id, name) VALUES ($1, $2)", exchangeID, req.Name)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ exchange_name表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", exchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 新增:插入短名称信息(对应exchange_short_name表)
|
||||
_, err = tx.Exec("INSERT INTO exchange_short_name (exchange_id, short_name) VALUES ($1, $2)", exchangeID, req.ShortName)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ exchange_short_name表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", exchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存短名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 插入代码信息
|
||||
_, err = tx.Exec("INSERT INTO exchange_code (exchange_id, code) VALUES ($1, $2)", exchangeID, req.Code)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ exchange_code表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", exchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存代码信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", exchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 交易所创建请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", exchangeID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
c.JSON(http.StatusOK, CreateResponse{
|
||||
Success: true,
|
||||
Message: "创建成功",
|
||||
Data: CreateData{
|
||||
ExchangeID: exchangeID,
|
||||
},
|
||||
})
|
||||
}
|
||||
184
backend/src/logic4exchange/delete.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package logic4exchange
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// DeleteRequest 删除请求参数结构
|
||||
type DeleteRequest struct {
|
||||
ExchangeID string `json:"exchange_id" binding:"required"` // 交易所ID,必填
|
||||
}
|
||||
|
||||
// DeleteResponse 删除响应结构
|
||||
type DeleteResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// DeleteHandler 处理删除逻辑(软删除)
|
||||
func DeleteHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-DeleteRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
zap.L().Info("📥 收到交易所删除请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req DeleteRequest
|
||||
// 绑定并验证请求参数
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:exchange_id为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
)
|
||||
|
||||
// 开启数据库事务
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 延迟处理panic情况
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 1. 更新exchange表
|
||||
_, err = tx.Exec("UPDATE exchange SET deleted = TRUE WHERE id = $1", req.ExchangeID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ exchange表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除交易所记录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 更新exchange_name表
|
||||
_, err = tx.Exec("UPDATE exchange_name SET deleted = TRUE WHERE exchange_id = $1", req.ExchangeID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ exchange_name表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 新增:更新exchange_short_name表(软删除短名称记录)
|
||||
_, err = tx.Exec("UPDATE exchange_short_name SET deleted = TRUE WHERE exchange_id = $1", req.ExchangeID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ exchange_short_name表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除短名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 更新exchange_code表
|
||||
_, err = tx.Exec("UPDATE exchange_code SET deleted = TRUE WHERE exchange_id = $1", req.ExchangeID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ exchange_code表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除代码信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 交易所删除请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, DeleteResponse{
|
||||
Success: true,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
240
backend/src/logic4exchange/read.go
Normal file
@@ -0,0 +1,240 @@
|
||||
package logic4exchange
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ReadRequest 读取请求参数结构
|
||||
type ReadRequest struct {
|
||||
ExchangeID string `form:"exchange_id"` // 交易所ID,可选
|
||||
Name string `form:"name"` // 交易所名称,可选
|
||||
Code string `form:"code"` // 交易所代码,可选
|
||||
ShortName string `form:"short_name"` // 交易所短名称,新增查询条件
|
||||
Page string `form:"page"` // 页码,可选
|
||||
PageSize string `form:"page_size"` // 每页条数,可选
|
||||
}
|
||||
|
||||
// ReadData 读取响应数据结构
|
||||
type ReadData struct {
|
||||
Total int64 `json:"total"` // 总条数
|
||||
Page int `json:"page"` // 当前页码
|
||||
PageSize int `json:"page_size"` // 每页条数
|
||||
Items []ExchangeInfoViewItem `json:"items"` // 数据列表
|
||||
}
|
||||
|
||||
// ExchangeInfoViewItem 视图数据项结构
|
||||
type ExchangeInfoViewItem struct {
|
||||
ExchangeID string `json:"exchange_id"` // 交易所ID
|
||||
Name string `json:"name"` // 交易所名称
|
||||
Code string `json:"code"` // 交易所代码
|
||||
ShortName string `json:"short_name"` // 新增:交易所短名称
|
||||
}
|
||||
|
||||
// ReadResponse 读取响应结构
|
||||
type ReadResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
Data ReadData `json:"data"` // 响应数据
|
||||
}
|
||||
|
||||
// ReadHandler 处理信息查询逻辑
|
||||
func ReadHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
reqID := c.Request.Header.Get("X-ReadRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
// 记录请求接收日志
|
||||
zap.L().Info("📥 收到交易所查询请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
// 绑定请求参数
|
||||
var req ReadRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数解析失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, ReadResponse{
|
||||
Success: false,
|
||||
Message: "请求参数格式错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证查询条件至少有一个不为空(新增short_name作为可选条件)
|
||||
if req.ExchangeID == "" && req.Name == "" && req.Code == "" && req.ShortName == "" {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("reason", "exchange_id、name、code、short_name不能同时为空"),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, ReadResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:exchange_id、name、code、short_name不能同时为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 处理分页参数默认值
|
||||
page, err := strconv.Atoi(req.Page)
|
||||
if err != nil || page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize, err := strconv.Atoi(req.PageSize)
|
||||
if err != nil || pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
zap.String("short_name", req.ShortName), // 新增短名称日志
|
||||
zap.Int("page", page),
|
||||
zap.Int("page_size", pageSize),
|
||||
)
|
||||
|
||||
// 构建查询条件和参数
|
||||
whereClauses := []string{}
|
||||
args := []interface{}{}
|
||||
paramIndex := 1
|
||||
|
||||
if req.ExchangeID != "" {
|
||||
whereClauses = append(whereClauses, "exchange_id = $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, req.ExchangeID)
|
||||
paramIndex++
|
||||
}
|
||||
if req.Name != "" {
|
||||
whereClauses = append(whereClauses, "name LIKE $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, "%"+req.Name+"%")
|
||||
paramIndex++
|
||||
}
|
||||
// 新增:短名称查询条件
|
||||
if req.ShortName != "" {
|
||||
whereClauses = append(whereClauses, "short_name LIKE $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, "%"+req.ShortName+"%")
|
||||
paramIndex++
|
||||
}
|
||||
if req.Code != "" {
|
||||
whereClauses = append(whereClauses, "code LIKE $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, "%"+req.Code+"%")
|
||||
paramIndex++
|
||||
}
|
||||
|
||||
// 构建基础SQL(新增查询short_name字段)
|
||||
baseSQL := "SELECT exchange_id, name, short_name, code FROM exchange_info_view"
|
||||
countSQL := "SELECT COUNT(*) FROM exchange_info_view"
|
||||
if len(whereClauses) > 0 {
|
||||
whereStr := " WHERE " + strings.Join(whereClauses, " AND ")
|
||||
baseSQL += whereStr
|
||||
countSQL += whereStr
|
||||
}
|
||||
|
||||
// 计算分页偏移量
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
// 拼接分页SQL
|
||||
querySQL := fmt.Sprintf("%s ORDER BY exchange_id LIMIT $%d OFFSET $%d", baseSQL, paramIndex, paramIndex+1)
|
||||
args = append(args, pageSize, offset)
|
||||
|
||||
// 查询总条数
|
||||
var total int64
|
||||
countArgs := args[:len(args)-2] // 排除分页参数
|
||||
err = db.DB.QueryRow(countSQL, countArgs...).Scan(&total)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 查询总条数失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 执行分页查询
|
||||
rows, err := db.DB.Query(querySQL, args...)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 分页查询失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// 处理查询结果(新增扫描short_name字段)
|
||||
var items []ExchangeInfoViewItem
|
||||
for rows.Next() {
|
||||
var item ExchangeInfoViewItem
|
||||
if err := rows.Scan(&item.ExchangeID, &item.Name, &item.ShortName, &item.Code); err != nil {
|
||||
zap.L().Error("❌ 解析查询结果失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "数据处理失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
// 检查行迭代过程中是否发生错误
|
||||
if err := rows.Err(); err != nil {
|
||||
zap.L().Error("❌ 行迭代错误",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 交易所查询请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Int64("total", total),
|
||||
zap.Int("page", page),
|
||||
zap.Int("page_size", pageSize),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, ReadResponse{
|
||||
Success: true,
|
||||
Message: "查询成功",
|
||||
Data: ReadData{
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Items: items,
|
||||
},
|
||||
})
|
||||
}
|
||||
208
backend/src/logic4exchange/update.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package logic4exchange
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// UpdateRequest 更新请求参数结构
|
||||
type UpdateRequest struct {
|
||||
ExchangeID string `json:"exchange_id" binding:"required"` // 交易所ID,必填
|
||||
Name string `json:"name"` // 交易所名称,可选
|
||||
Code string `json:"code"` // 交易所代码,可选
|
||||
ShortName string `json:"short_name"` // 新增:交易所短名称,可选
|
||||
}
|
||||
|
||||
// UpdateResponse 更新响应结构
|
||||
type UpdateResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// UpdateHandler 处理信息更新逻辑
|
||||
func UpdateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
reqID := c.Request.Header.Get("X-UpdateRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
// 记录请求接收日志
|
||||
zap.L().Info("📥 收到交易所更新请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req UpdateRequest
|
||||
// 绑定并验证请求参数(主要验证exchange_id必填)
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:exchange_id为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证name、code和short_name不能同时为空
|
||||
if req.Name == "" && req.Code == "" && req.ShortName == "" {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.String("reason", "name、code和short_name不能同时为空"),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:name、code和short_name不能同时为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
zap.String("short_name", req.ShortName), // 新增短名称日志
|
||||
)
|
||||
|
||||
// 开启数据库事务
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 延迟处理panic情况
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 如果name不为空,更新name表
|
||||
if req.Name != "" {
|
||||
_, err = tx.Exec("UPDATE exchange_name SET name = $1 WHERE exchange_id = $2", req.Name, req.ExchangeID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ exchange_name表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 name表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
)
|
||||
}
|
||||
|
||||
// 如果code不为空,更新code表
|
||||
if req.Code != "" {
|
||||
_, err = tx.Exec("UPDATE exchange_code SET code = $1 WHERE exchange_id = $2", req.Code, req.ExchangeID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ exchange_code表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新代码信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 code表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
)
|
||||
}
|
||||
|
||||
// 新增:如果short_name不为空,更新short_name表
|
||||
if req.ShortName != "" {
|
||||
_, err = tx.Exec("UPDATE exchange_short_name SET short_name = $1 WHERE exchange_id = $2", req.ShortName, req.ExchangeID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ exchange_short_name表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新短名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 short_name表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
)
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 交易所更新请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, UpdateResponse{
|
||||
Success: true,
|
||||
Message: "更新成功",
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package logic
|
||||
package logic4user
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"user/db"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
@@ -30,7 +30,7 @@ type LoginResponse struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// LoginHandler 处理用户登录请求的处理器函数
|
||||
// LoginHandler 处理登录请求的处理器函数
|
||||
// 参数c是gin.Context,用于获取请求信息和返回响应
|
||||
func LoginHandler(c *gin.Context) {
|
||||
// 获取请求ID,用于追踪请求链路,若请求头中没有则生成一个新的UUID
|
||||
@@ -54,7 +54,7 @@ func LoginHandler(c *gin.Context) {
|
||||
zap.Error(err),
|
||||
zap.Any("请求体", c.Request.Body),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, LoginResponse{
|
||||
c.JSON(http.StatusOK, LoginResponse{
|
||||
Success: false,
|
||||
Message: "账号或密码不能为空",
|
||||
})
|
||||
@@ -72,7 +72,7 @@ func LoginHandler(c *gin.Context) {
|
||||
zap.String("reqID", reqID),
|
||||
zap.String("账号", req.Account),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, LoginResponse{
|
||||
c.JSON(http.StatusOK, LoginResponse{
|
||||
Success: false,
|
||||
Message: "账号或密码不能为空",
|
||||
})
|
||||
@@ -85,7 +85,7 @@ func LoginHandler(c *gin.Context) {
|
||||
// 查询语句:从用户账号密码视图中查询指定账号(未删除)的密码和用户ID
|
||||
query := `
|
||||
SELECT password, user_id
|
||||
FROM user_account_password_view
|
||||
FROM user_info_view
|
||||
WHERE account = $1 AND deleted = false
|
||||
`
|
||||
zap.L().Info("💡 执行查询",
|
||||
@@ -1,9 +1,9 @@
|
||||
package logic
|
||||
package logic4user
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
"user/db"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
@@ -28,7 +28,7 @@ type RegisterResponse struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// registerHandler 处理用户注册逻辑
|
||||
// registerHandler 处理注册逻辑
|
||||
func RegisterHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-RegisterRequest-ID")
|
||||
@@ -70,7 +70,7 @@ func RegisterHandler(c *gin.Context) {
|
||||
query := `
|
||||
SELECT EXISTS(
|
||||
SELECT 1
|
||||
FROM user_account_password_view
|
||||
FROM user_info_view
|
||||
WHERE account = $1 AND deleted = false
|
||||
)
|
||||
`
|
||||
@@ -169,7 +169,7 @@ func RegisterHandler(c *gin.Context) {
|
||||
}
|
||||
zap.L().Info("✅ 密码加密成功", zap.String("req_id", reqID))
|
||||
|
||||
// 7. 插入user_account表
|
||||
// 7. 插入account表
|
||||
insertAccountQuery := `
|
||||
INSERT INTO user_account (user_id, account)
|
||||
VALUES ($1, $2)
|
||||
@@ -194,7 +194,7 @@ func RegisterHandler(c *gin.Context) {
|
||||
}
|
||||
zap.L().Info("✅ 账号信息保存成功", zap.String("req_id", reqID))
|
||||
|
||||
// 8. 插入user_password表
|
||||
// 8. 插入password表
|
||||
insertPasswordQuery := `
|
||||
INSERT INTO user_password (user_id, password)
|
||||
VALUES ($1, $2)
|
||||
235
backend/src/logic4variety/create.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package logic4variety
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// CreateRequest 创建品种请求参数结构
|
||||
type CreateRequest struct {
|
||||
Name string `json:"name" binding:"required"` // 品种名称,必填
|
||||
Code string `json:"code" binding:"required"` // 品种代码,必填
|
||||
Tick float64 `json:"tick" binding:"required"` // 品种tick,必填
|
||||
TickPrice float64 `json:"tick_price" binding:"required"` // 品种tick价格,必填
|
||||
ExchangeID string `json:"exchange_id" binding:"required"` // 交易所ID,必填
|
||||
}
|
||||
|
||||
// CreateResponse 创建品种响应结构
|
||||
type CreateResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data CreateData `json:"data"`
|
||||
}
|
||||
|
||||
// CreateData 响应数据结构
|
||||
type CreateData struct {
|
||||
VarietyID string `json:"variety_id"`
|
||||
}
|
||||
|
||||
// CreateHandler 处理创建品种逻辑
|
||||
func CreateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-VarietyCreateRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
zap.L().Info("📥 收到品种创建请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req CreateRequest
|
||||
// 绑定参数时会自动验证所有必填字段
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
zap.Any("request_body", c.Request.Body),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, CreateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:name、code、tick、tick_price和exchange_id为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
zap.Float64("tick", req.Tick),
|
||||
zap.Float64("tick_price", req.TickPrice),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
)
|
||||
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 1. 创建variety主记录
|
||||
var varietyID string
|
||||
err = tx.QueryRow("INSERT INTO variety DEFAULT VALUES RETURNING id").Scan(&varietyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "创建品种记录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("📝 variety表插入成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", varietyID),
|
||||
)
|
||||
|
||||
// 2. 插入交易所关联信息
|
||||
_, err = tx.Exec("INSERT INTO variety_exchange (variety_id, exchange_id) VALUES ($1, $2)", varietyID, req.ExchangeID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_exchange表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", varietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存交易所关联信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 插入名称信息
|
||||
_, err = tx.Exec("INSERT INTO variety_name (variety_id, name) VALUES ($1, $2)", varietyID, req.Name)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_name表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", varietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 插入代码信息
|
||||
_, err = tx.Exec("INSERT INTO variety_code (variety_id, code) VALUES ($1, $2)", varietyID, req.Code)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_code表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", varietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存代码信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 5. 插入tick信息
|
||||
_, err = tx.Exec("INSERT INTO variety_tick (variety_id, tick) VALUES ($1, $2)", varietyID, req.Tick)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_tick表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", varietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存tick信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 6. 插入tick价格信息
|
||||
_, err = tx.Exec("INSERT INTO variety_tick_price (variety_id, price) VALUES ($1, $2)", varietyID, req.TickPrice)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_tick_price表插入失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", varietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "保存tick价格信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", varietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, CreateResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 品种创建请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", varietyID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
c.JSON(http.StatusOK, CreateResponse{
|
||||
Success: true,
|
||||
Message: "创建成功",
|
||||
Data: CreateData{
|
||||
VarietyID: varietyID,
|
||||
},
|
||||
})
|
||||
}
|
||||
216
backend/src/logic4variety/delete.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package logic4variety
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// DeleteRequest 删除请求参数结构
|
||||
type DeleteRequest struct {
|
||||
VarietyID string `json:"variety_id" binding:"required"` // 品种ID,必填
|
||||
}
|
||||
|
||||
// DeleteResponse 删除响应结构
|
||||
type DeleteResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// DeleteHandler 处理删除逻辑(软删除)
|
||||
func DeleteHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-DeleteRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
zap.L().Info("📥 收到品种删除请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req DeleteRequest
|
||||
// 绑定并验证请求参数
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:variety_id为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
)
|
||||
|
||||
// 开启数据库事务
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 延迟处理panic情况
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 1. 更新variety表
|
||||
_, err = tx.Exec("UPDATE variety SET deleted = TRUE WHERE id = $1", req.VarietyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除品种记录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 更新variety_exchange表
|
||||
_, err = tx.Exec("UPDATE variety_exchange SET deleted = TRUE WHERE variety_id = $1", req.VarietyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_exchange表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除交易所关联信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 更新variety_name表
|
||||
_, err = tx.Exec("UPDATE variety_name SET deleted = TRUE WHERE variety_id = $1", req.VarietyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_name表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除名称信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 更新variety_code表
|
||||
_, err = tx.Exec("UPDATE variety_code SET deleted = TRUE WHERE variety_id = $1", req.VarietyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_code表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除代码信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 5. 更新variety_tick表
|
||||
_, err = tx.Exec("UPDATE variety_tick SET deleted = TRUE WHERE variety_id = $1", req.VarietyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_tick表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除最小变动价位信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 6. 更新variety_tick_price表
|
||||
_, err = tx.Exec("UPDATE variety_tick_price SET deleted = TRUE WHERE variety_id = $1", req.VarietyID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_tick_price表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "删除最小变动价位金额信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, DeleteResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 品种删除请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, DeleteResponse{
|
||||
Success: true,
|
||||
Message: "删除成功",
|
||||
})
|
||||
}
|
||||
243
backend/src/logic4variety/read.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package logic4variety
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ReadRequest 读取请求参数结构
|
||||
type ReadRequest struct {
|
||||
VarietyID string `form:"variety_id"` // 品种ID,可选
|
||||
Name string `form:"name"` // 品种名称,可选
|
||||
Code string `form:"code"` // 品种代码,可选
|
||||
ExchangeName string `form:"exchange_name"` // 交易所名称,可选
|
||||
Page string `form:"page"` // 页码,可选
|
||||
PageSize string `form:"page_size"` // 每页条数,可选
|
||||
}
|
||||
|
||||
// ReadData 读取响应数据结构
|
||||
type ReadData struct {
|
||||
Total int64 `json:"total"` // 总条数
|
||||
Page int `json:"page"` // 当前页码
|
||||
PageSize int `json:"page_size"` // 每页条数
|
||||
Items []VarietyInfoViewItem `json:"items"` // 数据列表
|
||||
}
|
||||
|
||||
// VarietyInfoViewItem 视图数据项结构
|
||||
type VarietyInfoViewItem struct {
|
||||
VarietyID string `json:"variety_id"` // 品种ID
|
||||
Name string `json:"name"` // 品种名称
|
||||
Code string `json:"code"` // 品种代码
|
||||
ExchangeName string `json:"exchange_name"` // 交易所名称
|
||||
Tick string `json:"tick"` // 最小变动价位
|
||||
TickPrice string `json:"tick_price"` // 最小变动价位对应的价值
|
||||
TickOriginal float64 `json:"tick_original"` // 最小变动价位(原始值)
|
||||
TickPriceOriginal float64 `json:"tick_price_original"` // 最小变动价位对应的价值(原始值)
|
||||
}
|
||||
|
||||
// ReadResponse 读取响应结构
|
||||
type ReadResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
Data ReadData `json:"data"` // 响应数据
|
||||
}
|
||||
|
||||
// ReadHandler 处理信息查询逻辑
|
||||
func ReadHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
reqID := c.Request.Header.Get("X-ReadRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
// 记录请求接收日志
|
||||
zap.L().Info("📥 收到品种查询请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
// 绑定请求参数
|
||||
var req ReadRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数解析失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, ReadResponse{
|
||||
Success: false,
|
||||
Message: "请求参数格式错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证查询条件至少有一个不为空
|
||||
if req.VarietyID == "" && req.Name == "" && req.Code == "" && req.ExchangeName == "" {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("reason", "variety_id、name、code、exchange_name不能同时为空"),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, ReadResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:variety_id、name、code、exchange_name不能同时为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 处理分页参数默认值
|
||||
page, err := strconv.Atoi(req.Page)
|
||||
if err != nil || page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize, err := strconv.Atoi(req.PageSize)
|
||||
if err != nil || pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
zap.String("exchange_name", req.ExchangeName),
|
||||
zap.Int("page", page),
|
||||
zap.Int("page_size", pageSize),
|
||||
)
|
||||
|
||||
// 构建查询条件和参数
|
||||
whereClauses := []string{}
|
||||
args := []interface{}{}
|
||||
paramIndex := 1
|
||||
|
||||
if req.VarietyID != "" {
|
||||
whereClauses = append(whereClauses, "variety_id = $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, req.VarietyID)
|
||||
paramIndex++
|
||||
}
|
||||
if req.Name != "" {
|
||||
whereClauses = append(whereClauses, "name LIKE $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, "%"+req.Name+"%")
|
||||
paramIndex++
|
||||
}
|
||||
if req.ExchangeName != "" {
|
||||
whereClauses = append(whereClauses, "exchange_name LIKE $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, "%"+req.ExchangeName+"%")
|
||||
paramIndex++
|
||||
}
|
||||
if req.Code != "" {
|
||||
whereClauses = append(whereClauses, "code LIKE $"+strconv.Itoa(paramIndex))
|
||||
args = append(args, "%"+req.Code+"%")
|
||||
paramIndex++
|
||||
}
|
||||
|
||||
// 构建基础SQL
|
||||
baseSQL := "SELECT variety_id, name, code, exchange_name, tick, tick_price, tick_original, tick_price_original FROM variety_info_view"
|
||||
countSQL := "SELECT COUNT(*) FROM variety_info_view"
|
||||
if len(whereClauses) > 0 {
|
||||
whereStr := " WHERE " + strings.Join(whereClauses, " AND ")
|
||||
baseSQL += whereStr
|
||||
countSQL += whereStr
|
||||
}
|
||||
|
||||
// 计算分页偏移量
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
// 拼接分页SQL
|
||||
querySQL := fmt.Sprintf("%s ORDER BY variety_id LIMIT $%d OFFSET $%d", baseSQL, paramIndex, paramIndex+1)
|
||||
args = append(args, pageSize, offset)
|
||||
|
||||
// 查询总条数
|
||||
var total int64
|
||||
countArgs := args[:len(args)-2] // 排除分页参数
|
||||
err = db.DB.QueryRow(countSQL, countArgs...).Scan(&total)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 查询总条数失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 执行分页查询
|
||||
rows, err := db.DB.Query(querySQL, args...)
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 分页查询失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// 处理查询结果
|
||||
var items []VarietyInfoViewItem
|
||||
for rows.Next() {
|
||||
var item VarietyInfoViewItem
|
||||
if err := rows.Scan(&item.VarietyID, &item.Name, &item.Code, &item.ExchangeName, &item.Tick, &item.TickPrice, &item.TickOriginal, &item.TickPriceOriginal); err != nil {
|
||||
zap.L().Error("❌ 解析查询结果失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "数据处理失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
// 检查行迭代过程中是否发生错误
|
||||
if err := rows.Err(); err != nil {
|
||||
zap.L().Error("❌ 行迭代错误",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, ReadResponse{
|
||||
Success: false,
|
||||
Message: "查询数据失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 品种查询请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Int64("total", total),
|
||||
zap.Int("page", page),
|
||||
zap.Int("page_size", pageSize),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, ReadResponse{
|
||||
Success: true,
|
||||
Message: "查询成功",
|
||||
Data: ReadData{
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Items: items,
|
||||
},
|
||||
})
|
||||
}
|
||||
370
backend/src/logic4variety/update.go
Normal file
@@ -0,0 +1,370 @@
|
||||
package logic4variety
|
||||
|
||||
import (
|
||||
"asset_assistant/db"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// UpdateRequest 更新请求参数结构
|
||||
type UpdateRequest struct {
|
||||
VarietyID string `json:"variety_id" binding:"required"` // 品种ID,必填
|
||||
ExchangeID string `json:"exchange_id"` // 交易所ID,可选
|
||||
ExchangeName string `json:"exchange_name"` // 交易所名称,可选
|
||||
Name string `json:"name"` // 品种名称,可选
|
||||
Code string `json:"code"` // 品种代码,可选
|
||||
Tick float64 `json:"tick"` // 最小变动价位,可选
|
||||
TickPrice float64 `json:"tick_price"` // 最小变动价值,可选
|
||||
}
|
||||
|
||||
// UpdateResponse 更新响应结构
|
||||
type UpdateResponse struct {
|
||||
Success bool `json:"success"` // 操作是否成功
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// UpdateHandler 处理信息更新逻辑
|
||||
func UpdateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
reqID := c.Request.Header.Get("X-UpdateRequest-ID")
|
||||
if reqID == "" {
|
||||
reqID = uuid.New().String()
|
||||
zap.L().Debug("✨ 生成新的请求ID", zap.String("req_id", reqID))
|
||||
}
|
||||
|
||||
// 记录请求接收日志
|
||||
zap.L().Info("📥 收到品种更新请求",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("method", c.Request.Method),
|
||||
)
|
||||
|
||||
var req UpdateRequest
|
||||
// 绑定并验证请求参数(主要验证variety_id必填)
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:variety_id为必填项",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证所有可选字段不能同时为空
|
||||
if req.ExchangeID == "" && req.ExchangeName == "" && req.Name == "" && req.Code == "" && req.Tick == 0 && req.TickPrice == 0 {
|
||||
zap.L().Warn("⚠️ 请求参数验证失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.String("reason", "所有更新字段不能同时为空"),
|
||||
)
|
||||
c.JSON(http.StatusBadRequest, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "请求参数错误:至少提供一个需要更新的字段",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Debug("✅ 请求参数验证通过",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.String("exchange_id", req.ExchangeID),
|
||||
zap.String("exchange_name", req.ExchangeName),
|
||||
zap.String("name", req.Name),
|
||||
zap.String("code", req.Code),
|
||||
zap.Float64("tick", req.Tick),
|
||||
zap.Float64("tick_price", req.TickPrice),
|
||||
)
|
||||
|
||||
// 开启数据库事务
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
zap.L().Error("❌ 事务开启失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 延迟处理panic情况
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if err := tx.Rollback(); err != nil {
|
||||
zap.L().Error("💥 panic后事务回滚失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
zap.L().Error("💥 事务处理发生panic",
|
||||
zap.String("req_id", reqID),
|
||||
zap.Any("recover", r),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "系统错误,请稍后重试",
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 如果exchange_id或exchange_name不为空,更新variety_exchange表
|
||||
if req.ExchangeID != "" || req.ExchangeName != "" {
|
||||
// 先查询是否已存在记录
|
||||
var count int
|
||||
err = tx.QueryRow("SELECT COUNT(*) FROM variety_exchange WHERE variety_id = $1 AND deleted = FALSE", req.VarietyID).Scan(&count)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_exchange表查询失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "查询交易所信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
// 更新现有记录
|
||||
_, err = tx.Exec("UPDATE variety_exchange SET exchange_id = $1, exchange_name = $2 WHERE variety_id = $3", req.ExchangeID, req.ExchangeName, req.VarietyID)
|
||||
} else {
|
||||
// 插入新记录
|
||||
_, err = tx.Exec("INSERT INTO variety_exchange (variety_id, exchange_id, exchange_name) VALUES ($1, $2, $3)", req.VarietyID, req.ExchangeID, req.ExchangeName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_exchange表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新交易所信息失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 variety_exchange表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
)
|
||||
}
|
||||
|
||||
// 如果name不为空,更新variety_name表
|
||||
if req.Name != "" {
|
||||
// 先查询是否已存在记录
|
||||
var count int
|
||||
err = tx.QueryRow("SELECT COUNT(*) FROM variety_name WHERE variety_id = $1 AND deleted = FALSE", req.VarietyID).Scan(&count)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_name表查询失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "查询品种名称失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
_, err = tx.Exec("UPDATE variety_name SET name = $1 WHERE variety_id = $2", req.Name, req.VarietyID)
|
||||
} else {
|
||||
_, err = tx.Exec("INSERT INTO variety_name (variety_id, name) VALUES ($1, $2)", req.VarietyID, req.Name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_name表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新品种名称失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 variety_name表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
)
|
||||
}
|
||||
|
||||
// 如果code不为空,更新variety_code表
|
||||
if req.Code != "" {
|
||||
// 先查询是否已存在记录
|
||||
var count int
|
||||
err = tx.QueryRow("SELECT COUNT(*) FROM variety_code WHERE variety_id = $1 AND deleted = FALSE", req.VarietyID).Scan(&count)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_code表查询失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "查询品种代码失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
_, err = tx.Exec("UPDATE variety_code SET code = $1 WHERE variety_id = $2", req.Code, req.VarietyID)
|
||||
} else {
|
||||
_, err = tx.Exec("INSERT INTO variety_code (variety_id, code) VALUES ($1, $2)", req.VarietyID, req.Code)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_code表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新品种代码失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 variety_code表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
)
|
||||
}
|
||||
|
||||
// 如果tick不为0,更新variety_tick表
|
||||
if req.Tick != 0 {
|
||||
// 先查询是否已存在记录
|
||||
var count int
|
||||
err = tx.QueryRow("SELECT COUNT(*) FROM variety_tick WHERE variety_id = $1 AND deleted = FALSE", req.VarietyID).Scan(&count)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_tick表查询失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "查询最小变动价位失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
_, err = tx.Exec("UPDATE variety_tick SET tick = $1 WHERE variety_id = $2", req.Tick, req.VarietyID)
|
||||
} else {
|
||||
_, err = tx.Exec("INSERT INTO variety_tick (variety_id, tick) VALUES ($1, $2)", req.VarietyID, req.Tick)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_tick表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新最小变动价位失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 variety_tick表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
)
|
||||
}
|
||||
|
||||
// 如果tick_price不为0,更新variety_tick_price表
|
||||
if req.TickPrice != 0 {
|
||||
// 先查询是否已存在记录
|
||||
var count int
|
||||
err = tx.QueryRow("SELECT COUNT(*) FROM variety_tick_price WHERE variety_id = $1 AND deleted = FALSE", req.VarietyID).Scan(&count)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_tick_price表查询失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "查询最小变动价值失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
_, err = tx.Exec("UPDATE variety_tick_price SET price = $1 WHERE variety_id = $2", req.TickPrice, req.VarietyID)
|
||||
} else {
|
||||
_, err = tx.Exec("INSERT INTO variety_tick_price (variety_id, price) VALUES ($1, $2)", req.VarietyID, req.TickPrice)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ variety_tick_price表更新失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "更新最小变动价值失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
zap.L().Debug("📝 variety_tick_price表更新成功",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
)
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit(); err != nil {
|
||||
tx.Rollback()
|
||||
zap.L().Error("❌ 事务提交失败",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, UpdateResponse{
|
||||
Success: false,
|
||||
Message: "数据提交失败,请稍后重试",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录请求处理耗时
|
||||
duration := time.Since(startTime)
|
||||
zap.L().Info("✅ 品种更新请求处理完成",
|
||||
zap.String("req_id", reqID),
|
||||
zap.String("variety_id", req.VarietyID),
|
||||
zap.Duration("duration", duration),
|
||||
)
|
||||
|
||||
// 返回成功响应
|
||||
c.JSON(http.StatusOK, UpdateResponse{
|
||||
Success: true,
|
||||
Message: "更新成功",
|
||||
})
|
||||
}
|
||||
99
backend/src/main.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"asset_assistant/db" // 数据库相关操作包
|
||||
"asset_assistant/logger" // 日志工具包
|
||||
"asset_assistant/logic4country"
|
||||
"asset_assistant/logic4currency"
|
||||
"asset_assistant/logic4exchange"
|
||||
"asset_assistant/logic4user"
|
||||
|
||||
// 业务逻辑处理包
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin" // Gin框架,用于构建HTTP服务
|
||||
_ "github.com/lib/pq" // PostgreSQL数据库驱动(下划线表示仅初始化不直接使用)
|
||||
"go.uber.org/zap" // Zap日志库,用于结构化日志输出
|
||||
)
|
||||
|
||||
// main函数是程序的入口点
|
||||
func main() {
|
||||
// 初始化日志配置
|
||||
logger.Init()
|
||||
// 记录服务初始化日志
|
||||
zap.L().Info("🚀 用户服务初始化")
|
||||
|
||||
// 记录数据库初始化开始日志
|
||||
zap.L().Info("⌛️ 数据库初始化开始")
|
||||
// 初始化数据库连接
|
||||
db.Init()
|
||||
// 程序退出时关闭数据库连接(defer确保在函数退出前执行)
|
||||
defer db.DB.Close()
|
||||
// 记录数据库初始化成功日志
|
||||
zap.L().Info("✅ 数据库初始化成功")
|
||||
|
||||
// 设置Gin框架为发布模式(关闭调试信息)
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
// 创建Gin默认路由器
|
||||
r := gin.Default()
|
||||
|
||||
// 配置跨域中间件
|
||||
r.Use(cors.New(cors.Config{
|
||||
// 允许所有来源(生产环境建议指定具体域名)
|
||||
AllowOrigins: []string{"*"},
|
||||
// 允许的请求方法
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
|
||||
// 允许的请求头
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "X-LoginRequest-ID"},
|
||||
// 允许前端读取的响应头
|
||||
ExposeHeaders: []string{"Content-Length"},
|
||||
// 是否允许携带cookie
|
||||
AllowCredentials: true,
|
||||
// 预检请求的缓存时间
|
||||
MaxAge: 12 * time.Hour,
|
||||
}))
|
||||
zap.L().Info("✅ 配置跨域中间件完成")
|
||||
|
||||
// 注册用户接口
|
||||
user := r.Group("/user")
|
||||
{
|
||||
user.POST("/register", logic4user.RegisterHandler)
|
||||
user.POST("/login", logic4user.LoginHandler)
|
||||
}
|
||||
zap.L().Info("✅ 用户接口注册完成")
|
||||
|
||||
// 注册国家接口
|
||||
country := r.Group("/country")
|
||||
{
|
||||
country.POST("/create", logic4country.CreateHandler)
|
||||
country.POST("/read", logic4country.ReadHandler)
|
||||
country.POST("/update", logic4country.UpdateHandler)
|
||||
country.POST("/delete", logic4country.DeleteHandler)
|
||||
}
|
||||
zap.L().Info("✅ 国家接口注册完成")
|
||||
|
||||
// 注册交易所接口
|
||||
exchange := r.Group("/exchange")
|
||||
{
|
||||
exchange.POST("/create", logic4exchange.CreateHandler)
|
||||
exchange.POST("/read", logic4exchange.ReadHandler)
|
||||
exchange.POST("/update", logic4exchange.UpdateHandler)
|
||||
exchange.POST("/delete", logic4exchange.DeleteHandler)
|
||||
}
|
||||
zap.L().Info("✅ 交易所接口注册完成")
|
||||
|
||||
// 注册货币接口
|
||||
currency := r.Group("/currency")
|
||||
{
|
||||
currency.POST("/create", logic4currency.CreateHandler)
|
||||
currency.POST("/read", logic4currency.ReadHandler)
|
||||
currency.POST("/update", logic4currency.UpdateHandler)
|
||||
currency.POST("/delete", logic4currency.DeleteHandler)
|
||||
}
|
||||
zap.L().Info("✅ 货币接口注册完成")
|
||||
|
||||
// 记录服务启动日志,监听80端口
|
||||
zap.L().Info("✅ 服务启动在80端口")
|
||||
r.Run(":80")
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
docker run -itd --name go_user_dev -v $(pwd)/src:/app -p 20000:80 golang:1.25.0-alpine3.22
|
||||
@@ -1,30 +0,0 @@
|
||||
-- 切换到目标数据库
|
||||
\c postgres;
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_user_modified_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user') THEN
|
||||
CREATE TABLE "user" ( -- user是关键字,用双引号包裹
|
||||
id UUID DEFAULT gen_random_uuid_v7() PRIMARY KEY NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_user_updated_at
|
||||
BEFORE UPDATE ON "user"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_user_modified_column();
|
||||
|
||||
RAISE NOTICE 'Created user table and trigger';
|
||||
ELSE
|
||||
RAISE NOTICE 'user table already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -1,32 +0,0 @@
|
||||
-- 切换到目标数据库
|
||||
\c postgres;
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_account_modified_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user_account') THEN
|
||||
CREATE TABLE user_account (
|
||||
id UUID DEFAULT gen_random_uuid_v7() PRIMARY KEY NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
account VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_user_account_updated_at
|
||||
BEFORE UPDATE ON "user_account"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_account_modified_column();
|
||||
|
||||
RAISE NOTICE 'Created user_account table and trigger';
|
||||
ELSE
|
||||
RAISE NOTICE 'user_account table already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -1,32 +0,0 @@
|
||||
-- 切换到目标数据库
|
||||
\c postgres;
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_password_modified_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'user_password') THEN
|
||||
CREATE TABLE user_password (
|
||||
id UUID DEFAULT gen_random_uuid_v7() PRIMARY KEY NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
password VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TRIGGER update_user_password_updated_at
|
||||
BEFORE UPDATE ON "user_password"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_password_modified_column();
|
||||
|
||||
RAISE NOTICE 'Created user_password table and trigger';
|
||||
ELSE
|
||||
RAISE NOTICE 'user_password table already exists';
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -1,36 +0,0 @@
|
||||
\c postgres;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
view_exists BOOLEAN;
|
||||
BEGIN
|
||||
-- 检查视图是否已存在
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.views
|
||||
WHERE table_name = 'user_account_password_view'
|
||||
) INTO view_exists;
|
||||
|
||||
-- 创建或更新视图
|
||||
CREATE OR REPLACE VIEW user_account_password_view AS
|
||||
SELECT
|
||||
u.id AS user_id,
|
||||
ua.account AS account,
|
||||
up.password AS password,
|
||||
u.deleted AS deleted
|
||||
FROM
|
||||
"user" u
|
||||
JOIN
|
||||
user_account ua ON u.id = ua.user_id
|
||||
JOIN
|
||||
user_password up ON u.id = up.user_id;
|
||||
|
||||
-- 根据视图是否已存在输出不同提示
|
||||
IF view_exists THEN
|
||||
RAISE NOTICE '视图 user_account_password_view 已更新';
|
||||
ELSE
|
||||
RAISE NOTICE '视图 user_account_password_view 已创建';
|
||||
END IF;
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE '处理视图时发生错误: %', SQLERRM;
|
||||
END $$;
|
||||
@@ -1,34 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"user/db"
|
||||
"user/logger"
|
||||
"user/logic"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/lib/pq"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger.Init()
|
||||
zap.L().Info("🚀 用户服务初始化")
|
||||
zap.L().Info("⌛️ 数据库初始化开始")
|
||||
db.Init()
|
||||
defer db.DB.Close() // 应用退出时关闭连接
|
||||
zap.L().Info("✅ 数据库初始化成功")
|
||||
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
r := gin.Default()
|
||||
|
||||
// 登录接口
|
||||
r.POST("/user/login", logic.LoginHandler)
|
||||
zap.L().Info("✅ 登录接口注册完成: POST /user/login")
|
||||
// 注册接口
|
||||
r.POST("/user/register", logic.RegisterHandler)
|
||||
zap.L().Info("✅ 登录接口注册完成: POST /user/register")
|
||||
|
||||
// 启动服务,监听80端口
|
||||
zap.L().Info("✅ 服务启动在80端口")
|
||||
r.Run(":80")
|
||||
}
|
||||
@@ -1,2 +1,53 @@
|
||||
# asset_assistant
|
||||
# Frontend
|
||||
|
||||
## 词条记录
|
||||
|
||||
假设你是一位经验丰富的前端开发人员,精通jquery的使用的前端项目的架构设计搭建,协助我完成资产管理的管理系统,先完成以下需求:
|
||||
|
||||
1、设计一个项目架构。
|
||||
|
||||
2、登录页面,输入账号密码登录,只有登录业务,没有注册和找回密码。
|
||||
|
||||
3、主页分为侧边栏、顶部导航、右边内容区。
|
||||
|
||||
4、基于 localStorage 的令牌验证,未登录自动跳转到登录页。
|
||||
|
||||
5、常用组件:
|
||||
|
||||
5.1、加载动画:全局加载状态提示。
|
||||
|
||||
5.2、消息提示:支持成功 / 错误 / 信息三种类型,自动消失。
|
||||
|
||||
5.3、菜单组件:支持多级菜单、折叠展开、激活状态。
|
||||
|
||||
5.4、页面管理:统一的页面加载机制,支持错误处理和重试功能。
|
||||
|
||||
6、扩展能力:尽量多的配置操作。
|
||||
|
||||
7、样式需求:该项目为金融性质项目,页面风格样式需要偏向暗夜模式。
|
||||
|
||||
---
|
||||
|
||||
假设你是一位经验丰富的 flutter 开发人员,精通项目的架构设计和搭建,协助我使用 flutter 开发 Web 项目,项目类型为资产管理性质的的管理系统,先完成以下需求:
|
||||
|
||||
1、设计一个项目架构。
|
||||
|
||||
2、登录页面,输入账号密码登录,只有登录业务,没有注册和找回密码。
|
||||
|
||||
3、主页分为侧边栏、顶部导航、右边内容区。
|
||||
|
||||
4、常用组件:
|
||||
|
||||
4.1、加载动画:全局加载状态提示。
|
||||
|
||||
4.2、消息提示:支持成功 / 错误 / 信息三种类型,自动消失。
|
||||
|
||||
4.3、菜单组件:支持多级菜单、折叠展开、激活状态。
|
||||
|
||||
5、扩展能力:尽量多的配置操作。
|
||||
|
||||
6、样式需求:该项目为金融性质项目,页面风格样式需要偏向暗夜模式。
|
||||
|
||||
7、项目名为:asset-assistant-system
|
||||
|
||||
---
|
||||
45
frontend/asset_assistant/.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
45
frontend/asset_assistant/.metadata
Normal file
@@ -0,0 +1,45 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
- platform: android
|
||||
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
- platform: ios
|
||||
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
- platform: linux
|
||||
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
- platform: macos
|
||||
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
- platform: web
|
||||
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
- platform: windows
|
||||
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
16
frontend/asset_assistant/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# asset_assistant
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
28
frontend/asset_assistant/analysis_options.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
14
frontend/asset_assistant/android/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
44
frontend/asset_assistant/android/app/build.gradle.kts
Normal file
@@ -0,0 +1,44 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.asset_assistant"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.asset_assistant"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,45 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="asset_assistant"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.example.asset_assistant
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
24
frontend/asset_assistant/android/build.gradle.kts
Normal file
@@ -0,0 +1,24 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
.get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
3
frontend/asset_assistant/android/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
5
frontend/asset_assistant/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
26
frontend/asset_assistant/android/settings.gradle.kts
Normal file
@@ -0,0 +1,26 @@
|
||||
pluginManagement {
|
||||
val flutterSdkPath =
|
||||
run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.9.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
34
frontend/asset_assistant/ios/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
26
frontend/asset_assistant/ios/Flutter/AppFrameworkInfo.plist
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>13.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
1
frontend/asset_assistant/ios/Flutter/Debug.xcconfig
Normal file
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
1
frontend/asset_assistant/ios/Flutter/Release.xcconfig
Normal file
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
619
frontend/asset_assistant/ios/Runner.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,619 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C8080294A63A400263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||
};
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C807F294A63A400263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C807D294A63A400263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = U67YL4F3XK;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.assetAssistant;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.assetAssistant.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.assetAssistant.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.assetAssistant.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = U67YL4F3XK;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.assetAssistant;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = U67YL4F3XK;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.assetAssistant;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C8088294A63A400263BE5 /* Debug */,
|
||||
331C8089294A63A400263BE5 /* Release */,
|
||||
331C808A294A63A400263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
7
frontend/asset_assistant/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||
BuildableName = "RunnerTests.xctest"
|
||||
BlueprintName = "RunnerTests"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
7
frontend/asset_assistant/ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
13
frontend/asset_assistant/ios/Runner/AppDelegate.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |