实现余额购买接口 & 实现全局 id 生成器
This commit is contained in:
17
README.md
17
README.md
@@ -11,7 +11,7 @@
|
|||||||
- [ ] 充值余额
|
- [ ] 充值余额
|
||||||
- [ ] 选择套餐
|
- [ ] 选择套餐
|
||||||
- [X] 提取 IP
|
- [X] 提取 IP
|
||||||
- [ ] 长效提取
|
- [ ] 长效提取
|
||||||
- [ ] 连接
|
- [ ] 连接
|
||||||
|
|
||||||
中间件:
|
中间件:
|
||||||
@@ -25,9 +25,10 @@
|
|||||||
业务代码和测试代码共用的控制变量可以优化为环境变量
|
业务代码和测试代码共用的控制变量可以优化为环境变量
|
||||||
|
|
||||||
channel 优化:
|
channel 优化:
|
||||||
- 重新梳理逻辑流程,简化循环
|
|
||||||
- 端口分配时加锁
|
- 重新梳理逻辑流程,简化循环
|
||||||
- 数据存入顺序,数据库 > 缓存 > 外部接口
|
- 端口分配时加锁
|
||||||
|
- 数据存入顺序,数据库 > 缓存 > 外部接口
|
||||||
|
|
||||||
remote 令牌问题
|
remote 令牌问题
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ oauth token 验证授权范围
|
|||||||
在 init/env 中有定义和默认值
|
在 init/env 中有定义和默认值
|
||||||
|
|
||||||
开发环境数据库迁移:
|
开发环境数据库迁移:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
pg-schema-diff apply --schema-dir .\scripts\sql --dsn "host=localhost user=test password=test dbname=app port=5432 sslmode=disable TimeZone=Asia/Shanghai"
|
pg-schema-diff apply --schema-dir .\scripts\sql --dsn "host=localhost user=test password=test dbname=app port=5432 sslmode=disable TimeZone=Asia/Shanghai"
|
||||||
```
|
```
|
||||||
@@ -66,6 +68,13 @@ pg-schema-diff apply --schema-dir .\scripts\sql --dsn "host=localhost user=test
|
|||||||
| proxy/shared-rotate | psr | 隧道代理 |
|
| proxy/shared-rotate | psr | 隧道代理 |
|
||||||
| proxy/private-static | pps | 独享代理 |
|
| proxy/private-static | pps | 独享代理 |
|
||||||
|
|
||||||
|
### 订单类型
|
||||||
|
|
||||||
|
| 枚举 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 1 | 充值余额 |
|
||||||
|
| 2 | 直接购买 |
|
||||||
|
|
||||||
### 外部服务
|
### 外部服务
|
||||||
|
|
||||||
服务器ip 110.40.82.248
|
服务器ip 110.40.82.248
|
||||||
|
|||||||
@@ -1678,10 +1678,10 @@
|
|||||||
{
|
{
|
||||||
"id": "UFwaLWIsE6V1u0wBoIki2",
|
"id": "UFwaLWIsE6V1u0wBoIki2",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"x": 1550.476799787434,
|
"x": 1580,
|
||||||
"y": 700.4507453599007,
|
"y": 700,
|
||||||
"width": 199.2013303179067,
|
"width": 140,
|
||||||
"height": 199.09850928019864,
|
"height": 200,
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
@@ -1697,11 +1697,11 @@
|
|||||||
"type": 2
|
"type": 2
|
||||||
},
|
},
|
||||||
"seed": 700454335,
|
"seed": 700454335,
|
||||||
"version": 1345,
|
"version": 1367,
|
||||||
"versionNonce": 1843048196,
|
"versionNonce": 321661628,
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
"updated": 1742973056826,
|
"updated": 1742973231871,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"points": [
|
"points": [
|
||||||
@@ -1710,19 +1710,19 @@
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
199.2013303179067,
|
140,
|
||||||
199.09850928019864
|
200
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "U3ry809DLSs3z2kp6IH9m",
|
"elementId": "U3ry809DLSs3z2kp6IH9m",
|
||||||
"focus": 0,
|
"focus": -0.3333333333333334,
|
||||||
"gap": 1
|
"gap": 1
|
||||||
},
|
},
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "02tdc6VRdsQxJlfDZOFSa",
|
"elementId": "02tdc6VRdsQxJlfDZOFSa",
|
||||||
"focus": 4.547473508864641e-15,
|
"focus": -0.3333333333333326,
|
||||||
"gap": 1
|
"gap": 1
|
||||||
},
|
},
|
||||||
"startArrowhead": "crowfoot_one",
|
"startArrowhead": "crowfoot_one",
|
||||||
@@ -1786,8 +1786,8 @@
|
|||||||
{
|
{
|
||||||
"id": "qFfFxWmqoOyrikrAZnpf6",
|
"id": "qFfFxWmqoOyrikrAZnpf6",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"x": 1300.0990604598142,
|
"x": 1300.0990604598144,
|
||||||
"y": 399.5491556175755,
|
"y": 399.09841025767486,
|
||||||
"width": 0.19812091962853629,
|
"width": 0.19812091962853629,
|
||||||
"height": 99.09831123515096,
|
"height": 99.09831123515096,
|
||||||
"angle": 0.0019992334198262185,
|
"angle": 0.0019992334198262185,
|
||||||
@@ -1805,11 +1805,11 @@
|
|||||||
"type": 2
|
"type": 2
|
||||||
},
|
},
|
||||||
"seed": 557170239,
|
"seed": 557170239,
|
||||||
"version": 500,
|
"version": 504,
|
||||||
"versionNonce": 1905164420,
|
"versionNonce": 703426753,
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
"updated": 1742973044311,
|
"updated": 1744102055508,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"points": [
|
"points": [
|
||||||
@@ -1825,15 +1825,15 @@
|
|||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "Bto4ODTDgZzQWKJot-An0",
|
"elementId": "Bto4ODTDgZzQWKJot-An0",
|
||||||
"focus": 0,
|
"focus": 4.547473508864641e-15,
|
||||||
"gap": 1
|
"gap": 1
|
||||||
},
|
},
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "1jrJPZuuyVEe_3htPZakP",
|
"elementId": "1jrJPZuuyVEe_3htPZakP",
|
||||||
"focus": 4.547473508864641e-15,
|
"focus": 2.2737367544323206e-15,
|
||||||
"gap": 1
|
"gap": 1
|
||||||
},
|
},
|
||||||
"startArrowhead": "crowfoot_one",
|
"startArrowhead": null,
|
||||||
"endArrowhead": "crowfoot_many",
|
"endArrowhead": "crowfoot_many",
|
||||||
"elbowed": false
|
"elbowed": false
|
||||||
},
|
},
|
||||||
@@ -2528,11 +2528,11 @@
|
|||||||
"index": "b2G",
|
"index": "b2G",
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"seed": 220752260,
|
"seed": 220752260,
|
||||||
"version": 71,
|
"version": 84,
|
||||||
"versionNonce": 1899879740,
|
"versionNonce": 1844124804,
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"boundElements": null,
|
"boundElements": [],
|
||||||
"updated": 1742973149462,
|
"updated": 1742973179481,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"text": "分配\nassign",
|
"text": "分配\nassign",
|
||||||
@@ -2548,10 +2548,10 @@
|
|||||||
{
|
{
|
||||||
"id": "R_-u_XvFXVoOYZdQkVqEP",
|
"id": "R_-u_XvFXVoOYZdQkVqEP",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"x": 1500.8926525754896,
|
"x": 1500,
|
||||||
"y": 973.3353179908356,
|
"y": 980,
|
||||||
"width": 201.09954604136533,
|
"width": 201.99219861685492,
|
||||||
"height": 137.1139381444691,
|
"height": 130.44925613530472,
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
@@ -2567,11 +2567,11 @@
|
|||||||
"type": 2
|
"type": 2
|
||||||
},
|
},
|
||||||
"seed": 1509277188,
|
"seed": 1509277188,
|
||||||
"version": 74,
|
"version": 79,
|
||||||
"versionNonce": 1128401540,
|
"versionNonce": 1522980412,
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"boundElements": null,
|
"boundElements": [],
|
||||||
"updated": 1742973114690,
|
"updated": 1742973247275,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"points": [
|
"points": [
|
||||||
@@ -2580,20 +2580,20 @@
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
201.09954604136533,
|
201.99219861685492,
|
||||||
137.1139381444691
|
130.44925613530472
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "sWVhyKNTaf6c4MTpx5iEK",
|
"elementId": "sWVhyKNTaf6c4MTpx5iEK",
|
||||||
"focus": -0.38461538461538525,
|
"focus": -0.3018059982941868,
|
||||||
"gap": 1
|
"gap": 1
|
||||||
},
|
},
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "P5uYFnh0fSXvOjdD6id2Z",
|
"elementId": "P5uYFnh0fSXvOjdD6id2Z",
|
||||||
"focus": -0.23076923076923048,
|
"focus": -0.20722399317674445,
|
||||||
"gap": 1
|
"gap": 1.0001133441418582
|
||||||
},
|
},
|
||||||
"startArrowhead": "crowfoot_one",
|
"startArrowhead": "crowfoot_one",
|
||||||
"endArrowhead": "crowfoot_many",
|
"endArrowhead": "crowfoot_many",
|
||||||
@@ -2624,7 +2624,7 @@
|
|||||||
"version": 152,
|
"version": 152,
|
||||||
"versionNonce": 257905468,
|
"versionNonce": 257905468,
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"boundElements": null,
|
"boundElements": [],
|
||||||
"updated": 1742973125284,
|
"updated": 1742973125284,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
|
|||||||
@@ -496,27 +496,27 @@ comment on column whitelist.deleted_at is '删除时间';
|
|||||||
drop table if exists channel cascade;
|
drop table if exists channel cascade;
|
||||||
create table channel (
|
create table channel (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
user_id int not null references "user" (id)
|
user_id int not null references "user" (id)
|
||||||
on update cascade
|
on update cascade
|
||||||
on delete cascade,
|
on delete cascade,
|
||||||
proxy_id int not null references proxy (id) --
|
proxy_id int not null references proxy (id) --
|
||||||
on update cascade --
|
on update cascade --
|
||||||
on delete set null,
|
on delete set null,
|
||||||
node_id int references node (id) --
|
node_id int references node (id) --
|
||||||
on update cascade --
|
on update cascade --
|
||||||
on delete set null,
|
on delete set null,
|
||||||
proxy_host varchar(255) not null default '',
|
proxy_host varchar(255) not null default '',
|
||||||
proxy_port int not null,
|
proxy_port int not null,
|
||||||
node_host varchar(255),
|
node_host varchar(255),
|
||||||
protocol varchar(255),
|
protocol varchar(255),
|
||||||
auth_ip bool not null default false,
|
auth_ip bool not null default false,
|
||||||
user_host varchar(255),
|
user_host varchar(255),
|
||||||
auth_pass bool not null default false,
|
auth_pass bool not null default false,
|
||||||
username varchar(255) unique,
|
username varchar(255) unique,
|
||||||
password varchar(255),
|
password varchar(255),
|
||||||
expiration timestamp not null,
|
expiration timestamp not null,
|
||||||
created_at timestamp default current_timestamp,
|
created_at timestamp default current_timestamp,
|
||||||
updated_at timestamp default current_timestamp,
|
updated_at timestamp default current_timestamp,
|
||||||
deleted_at timestamp
|
deleted_at timestamp
|
||||||
);
|
);
|
||||||
create index channel_user_id_index on channel (user_id);
|
create index channel_user_id_index on channel (user_id);
|
||||||
@@ -718,7 +718,7 @@ create table trade (
|
|||||||
remark varchar(255),
|
remark varchar(255),
|
||||||
amount decimal(12, 2) not null default 0,
|
amount decimal(12, 2) not null default 0,
|
||||||
payment decimal(12, 2) not null default 0,
|
payment decimal(12, 2) not null default 0,
|
||||||
method int not null default 0,
|
method int not null,
|
||||||
status int not null default 0,
|
status int not null default 0,
|
||||||
created_at timestamp default current_timestamp,
|
created_at timestamp default current_timestamp,
|
||||||
updated_at timestamp default current_timestamp,
|
updated_at timestamp default current_timestamp,
|
||||||
@@ -747,27 +747,29 @@ comment on column trade.deleted_at is '删除时间';
|
|||||||
-- bill
|
-- bill
|
||||||
drop table if exists bill cascade;
|
drop table if exists bill cascade;
|
||||||
create table bill (
|
create table bill (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
trade_id int not null references trade (id)
|
user_id int not null references "user" (id)
|
||||||
on update cascade
|
on update cascade
|
||||||
on delete cascade,
|
on delete cascade,
|
||||||
user_id int not null references "user" (id)
|
trade_id int references trade (id) --
|
||||||
on update cascade
|
on update cascade --
|
||||||
on delete cascade,
|
on delete set null,
|
||||||
product_id int references product (id) --
|
resource_id int references resource (id) --
|
||||||
on update cascade --
|
on update cascade --
|
||||||
on delete set null,
|
on delete set null,
|
||||||
info varchar(255),
|
bill_no varchar(255) not null unique,
|
||||||
count int default 0,
|
type int not null,
|
||||||
price decimal(12, 2) not null default 0,
|
info varchar(255),
|
||||||
amount decimal(12, 2) not null default 0,
|
amount decimal(12, 2) not null default 0,
|
||||||
payment decimal(12, 2) not null default 0,
|
payment decimal(12, 2) not null default 0,
|
||||||
created_at timestamp default current_timestamp,
|
created_at timestamp default current_timestamp,
|
||||||
updated_at timestamp default current_timestamp,
|
updated_at timestamp default current_timestamp,
|
||||||
deleted_at timestamp
|
deleted_at timestamp
|
||||||
);
|
);
|
||||||
|
create index bill_user_id_index on bill (user_id);
|
||||||
create index bill_trade_id_index on bill (trade_id);
|
create index bill_trade_id_index on bill (trade_id);
|
||||||
create index bill_product_id_index on bill (product_id);
|
create index bill_resource_id_index on bill (resource_id);
|
||||||
|
create index bill_type_index on bill (type);
|
||||||
create index bill_deleted_at_index on bill (deleted_at);
|
create index bill_deleted_at_index on bill (deleted_at);
|
||||||
|
|
||||||
-- bill表字段注释
|
-- bill表字段注释
|
||||||
@@ -775,10 +777,10 @@ comment on table bill is '账单表';
|
|||||||
comment on column bill.id is '账单ID';
|
comment on column bill.id is '账单ID';
|
||||||
comment on column bill.trade_id is '订单ID';
|
comment on column bill.trade_id is '订单ID';
|
||||||
comment on column bill.user_id is '用户ID';
|
comment on column bill.user_id is '用户ID';
|
||||||
comment on column bill.product_id is '产品ID';
|
comment on column bill.resource_id is '套餐ID';
|
||||||
|
comment on column bill.bill_no is '易读账单号';
|
||||||
comment on column bill.info is '产品可读信息';
|
comment on column bill.info is '产品可读信息';
|
||||||
comment on column bill.count is '购买数量';
|
comment on column bill.type is '账单类型:0-充值,1-消费,2-退款';
|
||||||
comment on column bill.price is '单价';
|
|
||||||
comment on column bill.amount is '总金额';
|
comment on column bill.amount is '总金额';
|
||||||
comment on column bill.payment is '支付金额';
|
comment on column bill.payment is '支付金额';
|
||||||
comment on column bill.created_at is '创建时间';
|
comment on column bill.created_at is '创建时间';
|
||||||
|
|||||||
140
web/handlers/resource.go
Normal file
140
web/handlers/resource.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"platform/web/auth"
|
||||||
|
m "platform/web/models"
|
||||||
|
q "platform/web/queries"
|
||||||
|
"platform/web/services"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// region CreateResourceByBalance
|
||||||
|
|
||||||
|
type CreateResourceByBalanceReq struct {
|
||||||
|
Type int32 `json:"type" validate:"required"`
|
||||||
|
Live int32 `json:"live" validate:"required"`
|
||||||
|
Expire int32 `json:"expire" validate:"required"`
|
||||||
|
Quota int32 `json:"quota" validate:"required"`
|
||||||
|
DailyLimit int32 `json:"daily_limit" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResourceByBalance 通过余额创建资源
|
||||||
|
func CreateResourceByBalance(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
// 检查权限
|
||||||
|
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析请求参数
|
||||||
|
req := new(CreateResourceByBalanceReq)
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = q.Q.Transaction(func(q *q.Query) error {
|
||||||
|
// 检查用户
|
||||||
|
user, err := q.User.Where(q.User.ID.Eq(authContext.Payload.Id)).Take()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算价格
|
||||||
|
var amount = 0
|
||||||
|
var payment = 0
|
||||||
|
|
||||||
|
// 检查余额
|
||||||
|
if user.Balance < float64(req.Quota)/100 {
|
||||||
|
return errors.New("余额不足")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建资源
|
||||||
|
resource := m.Resource{
|
||||||
|
UserID: authContext.Payload.Id,
|
||||||
|
}
|
||||||
|
err = q.Resource.Save(&resource)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resourcePss := m.ResourcePss{
|
||||||
|
ResourceID: resource.ID,
|
||||||
|
Type: req.Type,
|
||||||
|
Live: req.Live,
|
||||||
|
Quota: req.Quota,
|
||||||
|
Expire: time.Now().Add(time.Duration(req.Expire) * time.Second),
|
||||||
|
DailyLimit: req.DailyLimit,
|
||||||
|
}
|
||||||
|
err = q.ResourcePss.Save(&resourcePss)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户余额
|
||||||
|
user.Balance -= float64(payment)
|
||||||
|
_, err = q.User.
|
||||||
|
Where(q.User.ID.Eq(authContext.Payload.Id)).
|
||||||
|
Update(q.User.Balance, user.Balance)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成账单
|
||||||
|
bill := m.Bill{
|
||||||
|
UserID: authContext.Payload.Id,
|
||||||
|
ResourceID: resource.ID,
|
||||||
|
BillNo: services.ID.GenReadable("bil"),
|
||||||
|
Type: 1,
|
||||||
|
Info: "购买套餐",
|
||||||
|
Amount: float64(amount),
|
||||||
|
Payment: float64(payment),
|
||||||
|
}
|
||||||
|
err = q.Bill.Save(&bill)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region CreateResourceByAlipayCallback
|
||||||
|
|
||||||
|
type CreateResourceByAlipayCallbackReq struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResourceByAlipayCallback 支付宝支付回调
|
||||||
|
func CreateResourceByAlipayCallback(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
// 根据支付类型执行不同流程:
|
||||||
|
// 1. 支付宝或微信(即时支付)
|
||||||
|
// - 更新订单状态
|
||||||
|
// - 生成账单
|
||||||
|
// - 生成资源
|
||||||
|
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region CreateResourceByWechatCallback
|
||||||
|
|
||||||
|
type CreateResourceByWechatCallbackReq struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResourceByWechatCallback 微信支付回调
|
||||||
|
func CreateResourceByWechatCallback(c *fiber.Ctx) error {
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
61
web/handlers/trade.go
Normal file
61
web/handlers/trade.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"platform/web/auth"
|
||||||
|
m "platform/web/models"
|
||||||
|
q "platform/web/queries"
|
||||||
|
"platform/web/services"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// region CreateTrade
|
||||||
|
|
||||||
|
type CreateTradeReq struct {
|
||||||
|
Subject string `json:"subject" validate:"required"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
Amount int `json:"amount" validate:"required"`
|
||||||
|
Method int `json:"method" validate:"required"` // 支付方式:1.支付宝,2.微信
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTrade(c *fiber.Ctx) error {
|
||||||
|
// 检查权限
|
||||||
|
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析请求参数
|
||||||
|
req := new(CreateTradeReq)
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建交易订单
|
||||||
|
num, err := services.ID.GenSerial(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var trade = m.Trade{
|
||||||
|
UserID: authContext.Payload.Id,
|
||||||
|
InnerNo: strconv.FormatUint(num, 10),
|
||||||
|
Subject: req.Subject,
|
||||||
|
Remark: req.Remark,
|
||||||
|
Amount: float64(req.Amount) / 100,
|
||||||
|
Method: int32(req.Method),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用外部接口
|
||||||
|
|
||||||
|
// 保存交易订单
|
||||||
|
err = q.Trade.Create(&trade)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回结果,外部支付链接
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
@@ -14,18 +14,18 @@ const TableNameBill = "bill"
|
|||||||
|
|
||||||
// Bill mapped from table <bill>
|
// Bill mapped from table <bill>
|
||||||
type Bill struct {
|
type Bill struct {
|
||||||
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:账单ID" json:"id"` // 账单ID
|
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:账单ID" json:"id"` // 账单ID
|
||||||
OrderID int32 `gorm:"column:order_id;not null;comment:订单ID" json:"order_id"` // 订单ID
|
UserID int32 `gorm:"column:user_id;not null;comment:用户ID" json:"user_id"` // 用户ID
|
||||||
UserID int32 `gorm:"column:user_id;not null;comment:用户ID" json:"user_id"` // 用户ID
|
Info string `gorm:"column:info;comment:产品可读信息" json:"info"` // 产品可读信息
|
||||||
ProductID int32 `gorm:"column:product_id;comment:产品ID" json:"product_id"` // 产品ID
|
Amount float64 `gorm:"column:amount;not null;comment:总金额" json:"amount"` // 总金额
|
||||||
Info string `gorm:"column:info;comment:产品可读信息" json:"info"` // 产品可读信息
|
Payment float64 `gorm:"column:payment;not null;comment:支付金额" json:"payment"` // 支付金额
|
||||||
Count_ int32 `gorm:"column:count;comment:购买数量" json:"count"` // 购买数量
|
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
||||||
Price float64 `gorm:"column:price;not null;comment:单价" json:"price"` // 单价
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
||||||
Amount float64 `gorm:"column:amount;not null;comment:总金额" json:"amount"` // 总金额
|
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
|
||||||
Payment float64 `gorm:"column:payment;not null;comment:支付金额" json:"payment"` // 支付金额
|
TradeID int32 `gorm:"column:trade_id" json:"trade_id"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
ResourceID int32 `gorm:"column:resource_id" json:"resource_id"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
Type int32 `gorm:"column:type;not null" json:"type"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
|
BillNo string `gorm:"column:bill_no;not null" json:"bill_no"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName Bill's table name
|
// TableName Bill's table name
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ const TableNameRefund = "refund"
|
|||||||
// Refund mapped from table <refund>
|
// Refund mapped from table <refund>
|
||||||
type Refund struct {
|
type Refund struct {
|
||||||
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:退款ID" json:"id"` // 退款ID
|
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:退款ID" json:"id"` // 退款ID
|
||||||
OrderID int32 `gorm:"column:order_id;not null;comment:订单ID" json:"order_id"` // 订单ID
|
|
||||||
ProductID int32 `gorm:"column:product_id;comment:产品ID" json:"product_id"` // 产品ID
|
ProductID int32 `gorm:"column:product_id;comment:产品ID" json:"product_id"` // 产品ID
|
||||||
Amount float64 `gorm:"column:amount;not null;comment:退款金额" json:"amount"` // 退款金额
|
Amount float64 `gorm:"column:amount;not null;comment:退款金额" json:"amount"` // 退款金额
|
||||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
||||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
|
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
|
||||||
|
TradeID int32 `gorm:"column:trade_id;not null" json:"trade_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName Refund's table name
|
// TableName Refund's table name
|
||||||
|
|||||||
@@ -28,17 +28,17 @@ func newBill(db *gorm.DB, opts ...gen.DOOption) bill {
|
|||||||
tableName := _bill.billDo.TableName()
|
tableName := _bill.billDo.TableName()
|
||||||
_bill.ALL = field.NewAsterisk(tableName)
|
_bill.ALL = field.NewAsterisk(tableName)
|
||||||
_bill.ID = field.NewInt32(tableName, "id")
|
_bill.ID = field.NewInt32(tableName, "id")
|
||||||
_bill.OrderID = field.NewInt32(tableName, "order_id")
|
|
||||||
_bill.UserID = field.NewInt32(tableName, "user_id")
|
_bill.UserID = field.NewInt32(tableName, "user_id")
|
||||||
_bill.ProductID = field.NewInt32(tableName, "product_id")
|
|
||||||
_bill.Info = field.NewString(tableName, "info")
|
_bill.Info = field.NewString(tableName, "info")
|
||||||
_bill.Count_ = field.NewInt32(tableName, "count")
|
|
||||||
_bill.Price = field.NewFloat64(tableName, "price")
|
|
||||||
_bill.Amount = field.NewFloat64(tableName, "amount")
|
_bill.Amount = field.NewFloat64(tableName, "amount")
|
||||||
_bill.Payment = field.NewFloat64(tableName, "payment")
|
_bill.Payment = field.NewFloat64(tableName, "payment")
|
||||||
_bill.CreatedAt = field.NewTime(tableName, "created_at")
|
_bill.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
_bill.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_bill.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
_bill.DeletedAt = field.NewField(tableName, "deleted_at")
|
_bill.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
|
_bill.TradeID = field.NewInt32(tableName, "trade_id")
|
||||||
|
_bill.ResourceID = field.NewInt32(tableName, "resource_id")
|
||||||
|
_bill.Type = field.NewInt32(tableName, "type")
|
||||||
|
_bill.BillNo = field.NewString(tableName, "bill_no")
|
||||||
|
|
||||||
_bill.fillFieldMap()
|
_bill.fillFieldMap()
|
||||||
|
|
||||||
@@ -48,19 +48,19 @@ func newBill(db *gorm.DB, opts ...gen.DOOption) bill {
|
|||||||
type bill struct {
|
type bill struct {
|
||||||
billDo
|
billDo
|
||||||
|
|
||||||
ALL field.Asterisk
|
ALL field.Asterisk
|
||||||
ID field.Int32 // 账单ID
|
ID field.Int32 // 账单ID
|
||||||
OrderID field.Int32 // 订单ID
|
UserID field.Int32 // 用户ID
|
||||||
UserID field.Int32 // 用户ID
|
Info field.String // 产品可读信息
|
||||||
ProductID field.Int32 // 产品ID
|
Amount field.Float64 // 总金额
|
||||||
Info field.String // 产品可读信息
|
Payment field.Float64 // 支付金额
|
||||||
Count_ field.Int32 // 购买数量
|
CreatedAt field.Time // 创建时间
|
||||||
Price field.Float64 // 单价
|
UpdatedAt field.Time // 更新时间
|
||||||
Amount field.Float64 // 总金额
|
DeletedAt field.Field // 删除时间
|
||||||
Payment field.Float64 // 支付金额
|
TradeID field.Int32
|
||||||
CreatedAt field.Time // 创建时间
|
ResourceID field.Int32
|
||||||
UpdatedAt field.Time // 更新时间
|
Type field.Int32
|
||||||
DeletedAt field.Field // 删除时间
|
BillNo field.String
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
}
|
}
|
||||||
@@ -78,17 +78,17 @@ func (b bill) As(alias string) *bill {
|
|||||||
func (b *bill) updateTableName(table string) *bill {
|
func (b *bill) updateTableName(table string) *bill {
|
||||||
b.ALL = field.NewAsterisk(table)
|
b.ALL = field.NewAsterisk(table)
|
||||||
b.ID = field.NewInt32(table, "id")
|
b.ID = field.NewInt32(table, "id")
|
||||||
b.OrderID = field.NewInt32(table, "order_id")
|
|
||||||
b.UserID = field.NewInt32(table, "user_id")
|
b.UserID = field.NewInt32(table, "user_id")
|
||||||
b.ProductID = field.NewInt32(table, "product_id")
|
|
||||||
b.Info = field.NewString(table, "info")
|
b.Info = field.NewString(table, "info")
|
||||||
b.Count_ = field.NewInt32(table, "count")
|
|
||||||
b.Price = field.NewFloat64(table, "price")
|
|
||||||
b.Amount = field.NewFloat64(table, "amount")
|
b.Amount = field.NewFloat64(table, "amount")
|
||||||
b.Payment = field.NewFloat64(table, "payment")
|
b.Payment = field.NewFloat64(table, "payment")
|
||||||
b.CreatedAt = field.NewTime(table, "created_at")
|
b.CreatedAt = field.NewTime(table, "created_at")
|
||||||
b.UpdatedAt = field.NewTime(table, "updated_at")
|
b.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
b.DeletedAt = field.NewField(table, "deleted_at")
|
b.DeletedAt = field.NewField(table, "deleted_at")
|
||||||
|
b.TradeID = field.NewInt32(table, "trade_id")
|
||||||
|
b.ResourceID = field.NewInt32(table, "resource_id")
|
||||||
|
b.Type = field.NewInt32(table, "type")
|
||||||
|
b.BillNo = field.NewString(table, "bill_no")
|
||||||
|
|
||||||
b.fillFieldMap()
|
b.fillFieldMap()
|
||||||
|
|
||||||
@@ -107,17 +107,17 @@ func (b *bill) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
|||||||
func (b *bill) fillFieldMap() {
|
func (b *bill) fillFieldMap() {
|
||||||
b.fieldMap = make(map[string]field.Expr, 12)
|
b.fieldMap = make(map[string]field.Expr, 12)
|
||||||
b.fieldMap["id"] = b.ID
|
b.fieldMap["id"] = b.ID
|
||||||
b.fieldMap["order_id"] = b.OrderID
|
|
||||||
b.fieldMap["user_id"] = b.UserID
|
b.fieldMap["user_id"] = b.UserID
|
||||||
b.fieldMap["product_id"] = b.ProductID
|
|
||||||
b.fieldMap["info"] = b.Info
|
b.fieldMap["info"] = b.Info
|
||||||
b.fieldMap["count"] = b.Count_
|
|
||||||
b.fieldMap["price"] = b.Price
|
|
||||||
b.fieldMap["amount"] = b.Amount
|
b.fieldMap["amount"] = b.Amount
|
||||||
b.fieldMap["payment"] = b.Payment
|
b.fieldMap["payment"] = b.Payment
|
||||||
b.fieldMap["created_at"] = b.CreatedAt
|
b.fieldMap["created_at"] = b.CreatedAt
|
||||||
b.fieldMap["updated_at"] = b.UpdatedAt
|
b.fieldMap["updated_at"] = b.UpdatedAt
|
||||||
b.fieldMap["deleted_at"] = b.DeletedAt
|
b.fieldMap["deleted_at"] = b.DeletedAt
|
||||||
|
b.fieldMap["trade_id"] = b.TradeID
|
||||||
|
b.fieldMap["resource_id"] = b.ResourceID
|
||||||
|
b.fieldMap["type"] = b.Type
|
||||||
|
b.fieldMap["bill_no"] = b.BillNo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bill) clone(db *gorm.DB) bill {
|
func (b bill) clone(db *gorm.DB) bill {
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ func newRefund(db *gorm.DB, opts ...gen.DOOption) refund {
|
|||||||
tableName := _refund.refundDo.TableName()
|
tableName := _refund.refundDo.TableName()
|
||||||
_refund.ALL = field.NewAsterisk(tableName)
|
_refund.ALL = field.NewAsterisk(tableName)
|
||||||
_refund.ID = field.NewInt32(tableName, "id")
|
_refund.ID = field.NewInt32(tableName, "id")
|
||||||
_refund.OrderID = field.NewInt32(tableName, "order_id")
|
|
||||||
_refund.ProductID = field.NewInt32(tableName, "product_id")
|
_refund.ProductID = field.NewInt32(tableName, "product_id")
|
||||||
_refund.Amount = field.NewFloat64(tableName, "amount")
|
_refund.Amount = field.NewFloat64(tableName, "amount")
|
||||||
_refund.CreatedAt = field.NewTime(tableName, "created_at")
|
_refund.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
_refund.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_refund.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
_refund.DeletedAt = field.NewField(tableName, "deleted_at")
|
_refund.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
|
_refund.TradeID = field.NewInt32(tableName, "trade_id")
|
||||||
|
|
||||||
_refund.fillFieldMap()
|
_refund.fillFieldMap()
|
||||||
|
|
||||||
@@ -45,12 +45,12 @@ type refund struct {
|
|||||||
|
|
||||||
ALL field.Asterisk
|
ALL field.Asterisk
|
||||||
ID field.Int32 // 退款ID
|
ID field.Int32 // 退款ID
|
||||||
OrderID field.Int32 // 订单ID
|
|
||||||
ProductID field.Int32 // 产品ID
|
ProductID field.Int32 // 产品ID
|
||||||
Amount field.Float64 // 退款金额
|
Amount field.Float64 // 退款金额
|
||||||
CreatedAt field.Time // 创建时间
|
CreatedAt field.Time // 创建时间
|
||||||
UpdatedAt field.Time // 更新时间
|
UpdatedAt field.Time // 更新时间
|
||||||
DeletedAt field.Field // 删除时间
|
DeletedAt field.Field // 删除时间
|
||||||
|
TradeID field.Int32
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
}
|
}
|
||||||
@@ -68,12 +68,12 @@ func (r refund) As(alias string) *refund {
|
|||||||
func (r *refund) updateTableName(table string) *refund {
|
func (r *refund) updateTableName(table string) *refund {
|
||||||
r.ALL = field.NewAsterisk(table)
|
r.ALL = field.NewAsterisk(table)
|
||||||
r.ID = field.NewInt32(table, "id")
|
r.ID = field.NewInt32(table, "id")
|
||||||
r.OrderID = field.NewInt32(table, "order_id")
|
|
||||||
r.ProductID = field.NewInt32(table, "product_id")
|
r.ProductID = field.NewInt32(table, "product_id")
|
||||||
r.Amount = field.NewFloat64(table, "amount")
|
r.Amount = field.NewFloat64(table, "amount")
|
||||||
r.CreatedAt = field.NewTime(table, "created_at")
|
r.CreatedAt = field.NewTime(table, "created_at")
|
||||||
r.UpdatedAt = field.NewTime(table, "updated_at")
|
r.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
r.DeletedAt = field.NewField(table, "deleted_at")
|
r.DeletedAt = field.NewField(table, "deleted_at")
|
||||||
|
r.TradeID = field.NewInt32(table, "trade_id")
|
||||||
|
|
||||||
r.fillFieldMap()
|
r.fillFieldMap()
|
||||||
|
|
||||||
@@ -92,12 +92,12 @@ func (r *refund) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
|||||||
func (r *refund) fillFieldMap() {
|
func (r *refund) fillFieldMap() {
|
||||||
r.fieldMap = make(map[string]field.Expr, 7)
|
r.fieldMap = make(map[string]field.Expr, 7)
|
||||||
r.fieldMap["id"] = r.ID
|
r.fieldMap["id"] = r.ID
|
||||||
r.fieldMap["order_id"] = r.OrderID
|
|
||||||
r.fieldMap["product_id"] = r.ProductID
|
r.fieldMap["product_id"] = r.ProductID
|
||||||
r.fieldMap["amount"] = r.Amount
|
r.fieldMap["amount"] = r.Amount
|
||||||
r.fieldMap["created_at"] = r.CreatedAt
|
r.fieldMap["created_at"] = r.CreatedAt
|
||||||
r.fieldMap["updated_at"] = r.UpdatedAt
|
r.fieldMap["updated_at"] = r.UpdatedAt
|
||||||
r.fieldMap["deleted_at"] = r.DeletedAt
|
r.fieldMap["deleted_at"] = r.DeletedAt
|
||||||
|
r.fieldMap["trade_id"] = r.TradeID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r refund) clone(db *gorm.DB) refund {
|
func (r refund) clone(db *gorm.DB) refund {
|
||||||
|
|||||||
178
web/services/id.go
Normal file
178
web/services/id.go
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"platform/pkg/rds"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jxskiss/base62"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ID IdService = IdService{}
|
||||||
|
|
||||||
|
type IdService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// region SerialID
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 保留位,确保最高位为0,防止产生负值
|
||||||
|
reservedBits = 1
|
||||||
|
// 时间戳位数
|
||||||
|
timestampBits = 41
|
||||||
|
// 序列号位数
|
||||||
|
sequenceBits = 22
|
||||||
|
|
||||||
|
// 最大序列号掩码:2^22 - 1
|
||||||
|
maxSequence = (1 << sequenceBits) - 1
|
||||||
|
|
||||||
|
// 位移计算常量
|
||||||
|
timestampShift = sequenceBits
|
||||||
|
|
||||||
|
// Redis 缓存过期时间(秒)
|
||||||
|
redisTTL = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSequenceOverflow = errors.New("sequence overflow")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *IdService) GenSerial(ctx context.Context) (uint64, error) {
|
||||||
|
// 构造Redis键
|
||||||
|
now := time.Now().Unix()
|
||||||
|
key := idSerialKey(now)
|
||||||
|
|
||||||
|
// 使用Redis事务确保原子操作
|
||||||
|
var sequence int64
|
||||||
|
err := rds.Client.Watch(ctx, func(tx *redis.Tx) error {
|
||||||
|
|
||||||
|
// 获取当前序列号
|
||||||
|
currentVal, err := tx.Get(ctx, key).Int64()
|
||||||
|
if err != nil && !errors.Is(err, redis.Nil) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, redis.Nil) {
|
||||||
|
currentVal = 0
|
||||||
|
}
|
||||||
|
sequence = currentVal + 1
|
||||||
|
|
||||||
|
// 检查序列号是否溢出
|
||||||
|
if sequence > maxSequence {
|
||||||
|
return ErrSequenceOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将更新后的序列号保存回Redis,设置5秒过期时间
|
||||||
|
pipe := tx.Pipeline()
|
||||||
|
pipe.Set(ctx, key, sequence, redisTTL*time.Second)
|
||||||
|
_, err = pipe.Exec(ctx)
|
||||||
|
return err
|
||||||
|
}, key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装最终ID
|
||||||
|
id := uint64((now << timestampShift) | sequence)
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSerial 解析ID,返回其组成部分
|
||||||
|
func (s *IdService) ParseSerial(id uint64) (timestamp int64, sequence int64) {
|
||||||
|
// 通过位运算和掩码提取各部分
|
||||||
|
timestamp = int64(id >> timestampShift)
|
||||||
|
sequence = int64(id & maxSequence)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// idSerialKey 根据时间戳生成Redis键
|
||||||
|
func idSerialKey(timestamp int64) string {
|
||||||
|
return fmt.Sprintf("global:id:serial:%d", timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region ReadableID
|
||||||
|
|
||||||
|
// GenReadable 根据给定的标签生成易读的全局唯一标识符
|
||||||
|
// tag 参数用于标识 ID 的用途,如 "usr" 表示用户ID,"ord" 表示订单ID等
|
||||||
|
// 生成的 ID 格式为:<tag>_<encoded-uuid>,例如:usr_7NLmVLeHwqS73enFZ1i8tB
|
||||||
|
func (s *IdService) GenReadable(tag string) string {
|
||||||
|
// 生成 UUID
|
||||||
|
id := uuid.New()
|
||||||
|
|
||||||
|
// 将 UUID 编码为 Base62 字符串(更短,更易读)
|
||||||
|
encoded := base62.EncodeToString(id[:])
|
||||||
|
|
||||||
|
// 如标签为空,则直接返回编码后的字符串
|
||||||
|
if tag == "" {
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标准化标签:转换为小写并移除特殊字符
|
||||||
|
tag = normalizeTag(tag)
|
||||||
|
|
||||||
|
// 组合最终 ID
|
||||||
|
return fmt.Sprintf("%s_%s", tag, encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseReadableID 解析易读ID,返回其标签和编码部分
|
||||||
|
func (s *IdService) ParseReadableID(id string) (tag string, encoded string) {
|
||||||
|
parts := strings.SplitN(id, "_", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return "", id
|
||||||
|
}
|
||||||
|
return parts[0], parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryDecodeID 尝试将编码部分解码回 UUID
|
||||||
|
// 如果解码失败,返回错误
|
||||||
|
func (s *IdService) TryDecodeID(encoded string) (uuid.UUID, error) {
|
||||||
|
// 尝试解码 Base62 编码
|
||||||
|
bytes, err := base62.DecodeString(encoded)
|
||||||
|
if err != nil {
|
||||||
|
return uuid.UUID{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保长度正确
|
||||||
|
if len(bytes) != 16 {
|
||||||
|
return uuid.UUID{}, fmt.Errorf("invalid UUID length after decoding: %d", len(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为 UUID
|
||||||
|
var result uuid.UUID
|
||||||
|
copy(result[:], bytes)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeTag 标准化标签
|
||||||
|
// 转换为小写,移除特殊字符,最多保留 5 个字符
|
||||||
|
func normalizeTag(tag string) string {
|
||||||
|
// 转换为小写
|
||||||
|
tag = strings.ToLower(tag)
|
||||||
|
|
||||||
|
// 移除特殊字符
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, c := range tag {
|
||||||
|
if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') {
|
||||||
|
sb.WriteRune(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 截取最多 5 个字符
|
||||||
|
result := sb.String()
|
||||||
|
if len(result) > 5 {
|
||||||
|
result = result[:5]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
Reference in New Issue
Block a user