Compare commits
47 Commits
6f8b1d9b2b
...
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 |
12
backend/chat.md
Normal file
12
backend/chat.md
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"biz":“新增国家“,
|
||||
"data": [
|
||||
{
|
||||
"id": "",
|
||||
"name": "",
|
||||
"code": "",
|
||||
"flag": "",
|
||||
}
|
||||
],
|
||||
}
|
||||
---
|
||||
126
backend/prompt.md
Normal file
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中的信息。
|
||||
---
|
||||
@@ -1,28 +1,41 @@
|
||||
-- 切换到目标数据库
|
||||
\c postgres;
|
||||
-- =========================================================
|
||||
-- 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" ( -- user是关键字,用双引号包裹
|
||||
id UUID DEFAULT gen_random_uuid_v7() PRIMARY KEY NOT NULL,
|
||||
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 'created user table and trigger';
|
||||
BEFORE UPDATE ON "user"
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '1️⃣✅ user 主表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE 'user table already exists';
|
||||
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_v7() PRIMARY KEY NOT NULL,
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
user_id UUID NOT NULL,
|
||||
account VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
@@ -30,18 +43,17 @@ BEGIN
|
||||
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 'created user_account table and trigger';
|
||||
BEFORE UPDATE ON user_account
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '2️⃣✅ user_account 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE 'user_account table already exists';
|
||||
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_v7() PRIMARY KEY NOT NULL,
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
user_id UUID NOT NULL,
|
||||
password VARCHAR NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
@@ -49,55 +61,56 @@ BEGIN
|
||||
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 'created user_password table and trigger';
|
||||
BEFORE UPDATE ON user_password
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '3️⃣✅ user_password 子表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE 'user_password table already exists';
|
||||
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 '已删除旧视图 user_info_view';
|
||||
RAISE NOTICE '4️⃣♻️ 已删除旧视图 user_info_view';
|
||||
END IF;
|
||||
|
||||
-- 创建或更新视图
|
||||
CREATE OR REPLACE VIEW user_info_view AS
|
||||
SELECT
|
||||
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
|
||||
WHERE
|
||||
u.deleted = FALSE;
|
||||
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;
|
||||
|
||||
-- 根据视图是否已存在输出不同提示
|
||||
IF view_exists THEN
|
||||
RAISE NOTICE '视图 user_info_view 已更新(删除旧视图后重建)';
|
||||
ELSE
|
||||
RAISE NOTICE '视图 user_info_view 已创建';
|
||||
END IF;
|
||||
RAISE NOTICE '4️⃣✅ user_info_view 已创建/更新';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE '处理视图时发生错误: %', SQLERRM;
|
||||
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 $$;
|
||||
@@ -1,124 +1,137 @@
|
||||
-- 切换到目标数据库
|
||||
\c postgres;
|
||||
-- =========================================================
|
||||
-- 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_v7() PRIMARY KEY NOT NULL,
|
||||
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 'created country table and trigger';
|
||||
BEFORE UPDATE ON country
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '1️⃣✅ country 主表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE 'country table already exists';
|
||||
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_v7() PRIMARY KEY NOT NULL,
|
||||
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_name_updated_at
|
||||
BEFORE UPDATE ON "country_name"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_data_modified_column();
|
||||
|
||||
RAISE NOTICE 'created country_name table and trigger';
|
||||
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 'country_name table already exists';
|
||||
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_v7() PRIMARY KEY NOT NULL,
|
||||
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_code_updated_at
|
||||
BEFORE UPDATE ON "country_code"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_data_modified_column();
|
||||
|
||||
RAISE NOTICE 'created country_code table and trigger';
|
||||
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 'country_code table already exists';
|
||||
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_v7() PRIMARY KEY NOT NULL,
|
||||
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_flag_updated_at
|
||||
BEFORE UPDATE ON "country_flag"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_data_modified_column();
|
||||
|
||||
RAISE NOTICE 'created country_flag table and trigger';
|
||||
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 'country_flag table already exists';
|
||||
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 '已删除旧视图 country_info_view';
|
||||
RAISE NOTICE '4️⃣♻️ 已删除旧视图 country_info_view';
|
||||
END IF;
|
||||
|
||||
-- 创建或更新视图
|
||||
CREATE OR REPLACE VIEW country_info_view AS
|
||||
SELECT
|
||||
SELECT
|
||||
u.id AS country_id,
|
||||
n.name AS name, -- 国家名称
|
||||
c.code AS code, -- 国家代码
|
||||
f.flag AS 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; -- 只查询未删除的国家
|
||||
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;
|
||||
|
||||
-- 根据视图是否已存在输出不同提示
|
||||
IF view_exists THEN
|
||||
RAISE NOTICE '视图 country_info_view 已更新(删除旧视图后重建)';
|
||||
ELSE
|
||||
RAISE NOTICE '视图 country_info_view 已创建';
|
||||
END IF;
|
||||
RAISE NOTICE '4️⃣✅ country_info_view 已创建/更新';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE '处理视图时发生错误: %', SQLERRM;
|
||||
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 $$;
|
||||
@@ -1,124 +1,138 @@
|
||||
-- 切换到目标数据库
|
||||
\c postgres;
|
||||
-- =========================================================
|
||||
-- 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_v7() PRIMARY KEY NOT NULL,
|
||||
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 'created exchange table and trigger';
|
||||
BEFORE UPDATE ON exchange
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '1️⃣✅ exchange 主表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE 'exchange table already exists';
|
||||
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_v7() PRIMARY KEY NOT NULL,
|
||||
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_name_updated_at
|
||||
BEFORE UPDATE ON "exchange_name"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_data_modified_column();
|
||||
|
||||
RAISE NOTICE 'created exchange_name table and trigger';
|
||||
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 'exchange_name table already exists';
|
||||
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_v7() PRIMARY KEY NOT NULL,
|
||||
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_short_name_updated_at
|
||||
BEFORE UPDATE ON "exchange_short_name"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_data_modified_column();
|
||||
|
||||
RAISE NOTICE 'created exchange_short_name table and trigger';
|
||||
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 'exchange_short_name table already exists';
|
||||
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_v7() PRIMARY KEY NOT NULL,
|
||||
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_code_updated_at
|
||||
BEFORE UPDATE ON "exchange_code"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_data_modified_column();
|
||||
|
||||
RAISE NOTICE 'created exchange_code table and trigger';
|
||||
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 'exchange_code table already exists';
|
||||
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 '已删除旧视图 exchange_info_view';
|
||||
RAISE NOTICE '4️⃣♻️ 已删除旧视图 exchange_info_view';
|
||||
END IF;
|
||||
|
||||
-- 重新创建视图(无冲突风险)
|
||||
CREATE VIEW exchange_info_view AS
|
||||
SELECT
|
||||
CREATE OR REPLACE VIEW exchange_info_view AS
|
||||
SELECT
|
||||
u.id AS exchange_id,
|
||||
n.name AS name,
|
||||
sn.short_name AS short_name,
|
||||
c.code AS code,
|
||||
u.deleted AS 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;
|
||||
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;
|
||||
|
||||
IF view_exists THEN
|
||||
RAISE NOTICE '视图 exchange_info_view 已更新(删除旧视图后重建)';
|
||||
ELSE
|
||||
RAISE NOTICE '视图 exchange_info_view 已创建';
|
||||
END IF;
|
||||
RAISE NOTICE '4️⃣✅ exchange_info_view 已创建/更新';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE '处理视图时发生错误: %', SQLERRM;
|
||||
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 $$;
|
||||
@@ -1,103 +1,117 @@
|
||||
-- 切换到目标数据库
|
||||
\c postgres;
|
||||
-- =========================================================
|
||||
-- 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_v7() PRIMARY KEY NOT NULL,
|
||||
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 'created currency table and trigger';
|
||||
BEFORE UPDATE ON currency
|
||||
FOR EACH ROW EXECUTE FUNCTION update_data_modified_column();
|
||||
RAISE NOTICE '1️⃣✅ currency 主表已创建';
|
||||
ELSE
|
||||
RAISE NOTICE 'currency table already exists';
|
||||
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_v7() PRIMARY KEY NOT NULL,
|
||||
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_name_updated_at
|
||||
BEFORE UPDATE ON "currency_name"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_data_modified_column();
|
||||
|
||||
RAISE NOTICE 'created currency_name table and trigger';
|
||||
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 'currency_name table already exists';
|
||||
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_v7() PRIMARY KEY NOT NULL,
|
||||
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_code_updated_at
|
||||
BEFORE UPDATE ON "currency_code"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_data_modified_column();
|
||||
|
||||
RAISE NOTICE 'created currency_code table and trigger';
|
||||
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 'currency_code table already exists';
|
||||
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 '已删除旧视图 currency_info_view';
|
||||
RAISE NOTICE '4️⃣♻️ 已删除旧视图 currency_info_view';
|
||||
END IF;
|
||||
|
||||
-- 创建或更新视图
|
||||
CREATE OR REPLACE VIEW currency_info_view AS
|
||||
SELECT
|
||||
SELECT
|
||||
u.id AS currency_id,
|
||||
n.name AS name,
|
||||
c.code AS code,
|
||||
u.deleted AS deleted
|
||||
FROM
|
||||
currency u
|
||||
JOIN
|
||||
currency_name n ON u.id = n.currency_id
|
||||
JOIN
|
||||
currency_code c ON u.id = c.currency_id
|
||||
WHERE
|
||||
u.deleted = FALSE;
|
||||
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;
|
||||
|
||||
-- 根据视图是否已存在输出不同提示
|
||||
IF view_exists THEN
|
||||
RAISE NOTICE '视图 currency_info_view 已更新(删除旧视图后重建)';
|
||||
ELSE
|
||||
RAISE NOTICE '视图 currency_info_view 已创建';
|
||||
END IF;
|
||||
RAISE NOTICE '4️⃣✅ currency_info_view 已创建/更新';
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE '处理视图时发生错误: %', SQLERRM;
|
||||
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 $$;
|
||||
@@ -1,13 +1,19 @@
|
||||
-- =========================================================
|
||||
-- variety.sql
|
||||
-- 服务器一次性部署:原结构 + 性能优化
|
||||
-- PostgreSQL 17.4+
|
||||
-- variety.sql 🎉
|
||||
-- 无物化视图 | 超可视提示 | 可重复执行
|
||||
-- PostgreSQL 17.4+ 👍
|
||||
-- =========================================================
|
||||
|
||||
-- 1. 连接目标库(按需改名字)
|
||||
-- 1️⃣ 开始 🚀
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🚀============ variety 部署开始 ============🚀';
|
||||
END $$;
|
||||
|
||||
-- 2️⃣ 连接目标库
|
||||
\c postgres;
|
||||
|
||||
-- 2. 表结构 -----------------------------------
|
||||
-- 3️⃣ 表结构 -----------------------------------
|
||||
DO $$
|
||||
BEGIN
|
||||
-- variety 主表
|
||||
@@ -18,6 +24,9 @@ BEGIN
|
||||
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
|
||||
@@ -26,6 +35,7 @@ BEGIN
|
||||
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
|
||||
@@ -33,6 +43,9 @@ BEGIN
|
||||
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
|
||||
@@ -48,6 +61,9 @@ BEGIN
|
||||
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
|
||||
@@ -63,6 +79,9 @@ BEGIN
|
||||
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
|
||||
@@ -78,6 +97,9 @@ BEGIN
|
||||
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
|
||||
@@ -93,82 +115,51 @@ BEGIN
|
||||
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 $$;
|
||||
|
||||
-- 3. 视图 --------------------------------------
|
||||
-- 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
|
||||
IF EXISTS (SELECT 1 FROM information_schema.views WHERE table_name = 'variety_info_view') THEN
|
||||
DROP VIEW variety_info_view;
|
||||
END IF;
|
||||
|
||||
CREATE OR REPLACE VIEW variety_info_view AS
|
||||
SELECT
|
||||
v.id AS variety_id,
|
||||
vn.name,
|
||||
vc.code,
|
||||
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_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;
|
||||
RAISE NOTICE '7️⃣✅ variety_info_view 已创建/更新';
|
||||
END $$;
|
||||
|
||||
-- 4. 性能优化:索引 -----------------------------------------
|
||||
-- 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);
|
||||
|
||||
-- 5. 性能优化:轻量视图 + 物化视图 --------------------------
|
||||
CREATE OR REPLACE VIEW variety_info_view_lean AS
|
||||
SELECT
|
||||
v.id AS variety_id,
|
||||
vn.name,
|
||||
vc.code,
|
||||
vt.tick AS tick_original,
|
||||
vtp.price AS tick_price_original
|
||||
FROM variety v
|
||||
LEFT JOIN variety_name vn ON vn.variety_id = v.id AND vn.deleted = FALSE
|
||||
LEFT JOIN variety_code vc ON vc.variety_id = v.id AND vc.deleted = FALSE
|
||||
LEFT JOIN variety_tick vt ON vt.variety_id = v.id AND vt.deleted = FALSE
|
||||
LEFT JOIN variety_tick_price vtp ON vtp.variety_id = v.id AND vtp.deleted = FALSE
|
||||
WHERE v.deleted = FALSE;
|
||||
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS variety_info_mv AS
|
||||
SELECT * FROM variety_info_view_lean;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_variety_info_mv ON variety_info_mv(variety_id);
|
||||
|
||||
-- 6. 刷新函数 ------------------------------------------------
|
||||
CREATE OR REPLACE FUNCTION refresh_variety_info_mv()
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
REFRESH MATERIALIZED VIEW CONCURRENTLY variety_info_mv;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 7. 首次填充
|
||||
PERFORM refresh_variety_info_mv();
|
||||
|
||||
-- 8. 小贴士 ----------------------------------------------
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '
|
||||
=== 部署完成 ===
|
||||
1. 若从旧库迁数据,可执行:
|
||||
pg_dump -U old_user -t variety -t variety_* old_db | psql -U new_user -d new_db
|
||||
2. 刷新物化视图:SELECT refresh_variety_info_mv();
|
||||
3. 建议 cron 每 15 min 刷新一次:
|
||||
*/15 * * * * psql -U new_user -d new_db -c "SELECT refresh_variety_info_mv();"
|
||||
';
|
||||
RAISE NOTICE '8️⃣✅ 全部索引已确保存在';
|
||||
END $$;
|
||||
|
||||
-- 6️⃣ 完成 🎉
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '🎉============ variety 部署完成 ============🎉';
|
||||
END $$;
|
||||
286
backend/sql/08_trade.sql
Normal file
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 $$;
|
||||
@@ -14,17 +14,17 @@ import (
|
||||
var shanghaiLoc *time.Location
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
shanghaiLoc, err = time.LoadLocation("Asia/Shanghai")
|
||||
if err != nil {
|
||||
// 尝试备选时区名称
|
||||
shanghaiLoc, err = time.LoadLocation("PRC")
|
||||
if err != nil {
|
||||
// 若仍失败,手动设置东八区偏移
|
||||
shanghaiLoc = time.FixedZone("CST", 8*3600)
|
||||
log.Printf("警告:加载时区失败,使用手动东八区偏移: %v", err)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
shanghaiLoc, err = time.LoadLocation("Asia/Shanghai")
|
||||
if err != nil {
|
||||
// 尝试备选时区名称
|
||||
shanghaiLoc, err = time.LoadLocation("PRC")
|
||||
if err != nil {
|
||||
// 若仍失败,手动设置东八区偏移
|
||||
shanghaiLoc = time.FixedZone("CST", 8*3600)
|
||||
log.Printf("警告:加载时区失败,使用手动东八区偏移: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Init 初始化日志(依赖配置文件已加载)
|
||||
@@ -43,10 +43,10 @@ func Init() {
|
||||
// 日志轮转配置(lumberjack)
|
||||
hook := lumberjack.Logger{
|
||||
Filename: viper.GetString("logger.path") + "logs/app.log", // 日志文件路径
|
||||
MaxSize: viper.GetInt("logger.max_size"), // 单个文件最大大小(MB)
|
||||
MaxBackups: viper.GetInt("logger.max_backup"), // 最大备份数
|
||||
MaxAge: viper.GetInt("logger.max_age"), // 最大保留天数
|
||||
Compress: true, // 是否压缩
|
||||
MaxSize: viper.GetInt("logger.max_size"), // 单个文件最大大小(MB)
|
||||
MaxBackups: viper.GetInt("logger.max_backup"), // 最大备份数
|
||||
MaxAge: viper.GetInt("logger.max_age"), // 最大保留天数
|
||||
Compress: true, // 是否压缩
|
||||
}
|
||||
|
||||
// 编码器配置
|
||||
@@ -83,4 +83,4 @@ func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||
beijingTime := t.In(shanghaiLoc)
|
||||
// 格式化输出
|
||||
enc.AppendString(beijingTime.Format("2006-01-02 15:04:05.000"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ type CreateData struct {
|
||||
CountryID string `json:"country_id"`
|
||||
}
|
||||
|
||||
// CreateHandler 处理国家创建逻辑
|
||||
// CreateHandler 处理创建逻辑
|
||||
func CreateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-RegisterRequest-ID")
|
||||
|
||||
@@ -21,7 +21,7 @@ type DeleteResponse struct {
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// DeleteHandler 处理国家删除逻辑(软删除)
|
||||
// DeleteHandler 处理删除逻辑(软删除)
|
||||
func DeleteHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-DeleteRequest-ID")
|
||||
|
||||
@@ -47,7 +47,7 @@ type ReadResponse struct {
|
||||
Data ReadData `json:"data"` // 响应数据
|
||||
}
|
||||
|
||||
// ReadHandler 处理国家信息查询逻辑
|
||||
// ReadHandler 处理查询逻辑
|
||||
func ReadHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
|
||||
@@ -24,7 +24,7 @@ type UpdateResponse struct {
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// UpdateHandler 处理国家信息更新逻辑
|
||||
// UpdateHandler 更新逻辑
|
||||
func UpdateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
@@ -229,4 +229,4 @@ func UpdateHandler(c *gin.Context) {
|
||||
Success: true,
|
||||
Message: "更新成功",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ type CreateData struct {
|
||||
CurrencyID string `json:"currency_id"` // 货币唯一标识ID
|
||||
}
|
||||
|
||||
// CreateHandler 处理货币创建逻辑
|
||||
// 接收HTTP请求,完成参数验证、数据库事务处理并返回响应
|
||||
// CreateHandler 处理创建逻辑
|
||||
func CreateHandler(c *gin.Context) {
|
||||
startTime := time.Now() // 记录请求开始时间,用于统计耗时
|
||||
// 获取或生成请求ID,用于追踪整个请求链路
|
||||
|
||||
@@ -21,7 +21,7 @@ type DeleteResponse struct {
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// DeleteHandler 处理货币删除逻辑(软删除)
|
||||
// DeleteHandler 处理删除逻辑(软删除)
|
||||
func DeleteHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-DeleteRequest-ID")
|
||||
|
||||
@@ -45,7 +45,7 @@ type ReadResponse struct {
|
||||
Data ReadData `json:"data"` // 响应数据
|
||||
}
|
||||
|
||||
// ReadHandler 处理货币信息查询逻辑
|
||||
// ReadHandler 处理信息查询逻辑
|
||||
func ReadHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
|
||||
@@ -23,7 +23,7 @@ type UpdateResponse struct {
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// UpdateHandler 处理货币信息更新逻辑
|
||||
// UpdateHandler 处理信息更新逻辑
|
||||
func UpdateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
|
||||
@@ -30,7 +30,7 @@ type CreateData struct {
|
||||
ExchangeID string `json:"exchange_id"`
|
||||
}
|
||||
|
||||
// CreateHandler 处理交易所创建逻辑
|
||||
// CreateHandler 处理创建逻辑
|
||||
func CreateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-RegisterRequest-ID")
|
||||
|
||||
@@ -21,7 +21,7 @@ type DeleteResponse struct {
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// DeleteHandler 处理交易所删除逻辑(软删除)
|
||||
// DeleteHandler 处理删除逻辑(软删除)
|
||||
func DeleteHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
reqID := c.Request.Header.Get("X-DeleteRequest-ID")
|
||||
|
||||
@@ -47,7 +47,7 @@ type ReadResponse struct {
|
||||
Data ReadData `json:"data"` // 响应数据
|
||||
}
|
||||
|
||||
// ReadHandler 处理交易所信息查询逻辑
|
||||
// ReadHandler 处理信息查询逻辑
|
||||
func ReadHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
|
||||
@@ -24,7 +24,7 @@ type UpdateResponse struct {
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// UpdateHandler 处理交易所信息更新逻辑
|
||||
// UpdateHandler 处理信息更新逻辑
|
||||
func UpdateHandler(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
// 获取或生成请求ID
|
||||
|
||||
@@ -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: "账号或密码不能为空",
|
||||
})
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1 +1,235 @@
|
||||
package logic4variety
|
||||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1 +1,216 @@
|
||||
package logic4variety
|
||||
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: "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1 +1,243 @@
|
||||
package logic4variety
|
||||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1 +1,370 @@
|
||||
package logic4variety
|
||||
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: "更新成功",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
分析这个项目,在 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、将查找的数据分页返回。
|
||||
---
|
||||
Reference in New Issue
Block a user