15 Commits

63 changed files with 3392 additions and 622 deletions

57
.env.example Normal file
View File

@@ -0,0 +1,57 @@
# 应用配置
RUN_MODE=development
DEBUG_HTTP_DUMP=false
# 数据库配置
DB_HOST=127.0.0.1
DB_PORT=5432
DB_NAME=app
DB_USERNAME=dev
DB_PASSWORD=dev
# redis 配置
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
# otel 配置
OTEL_HOST=127.0.0.1
OTEL_PORT=4317
# 白银节点
BAIYIN_CLOUD_URL=
BAIYIN_TOKEN_URL=
# 京东实名
IDEN_ACCESS_KEY=
IDEN_SECRET_KEY=
IDEN_CALLBACK_URL=
# 支付宝(暂时弃用,但是需要配置)
ALIPAY_APP_ID=
ALIPAY_APP_PRIVATE_KEY=
ALIPAY_PUBLIC_KEY=
ALIPAY_API_CERT=
# 微信支付(暂时弃用,但是需要配置)
WECHATPAY_APP_ID=
WECHATPAY_MCH_ID=
WECHATPAY_MCH_PRIVATE_KEY_SERIAL=
WECHATPAY_MCH_PRIVATE_KEY=
WECHATPAY_PUBLIC_KEY_ID=
WECHATPAY_PUBLIC_KEY=
WECHATPAY_API_CERT=
WECHATPAY_CALLBACK_URL=
# 阿里云
ALIYUN_ACCESS_KEY=
ALIYUN_ACCESS_KEY_SECRET=
ALIYUN_SMS_SIGNATURE=
ALIYUN_SMS_TEMPLATE_LOGIN=
# 商福通
SFTPAY_ENABLE=
SFTPAY_APP_ID=
SFTPAY_ROUTE_ID=
SFTPAY_APP_PRIVATE_KEY=
SFTPAY_PUBLIC_KEY=
SFTPAY_RETURN_URL=

View File

@@ -19,7 +19,7 @@ WORKDIR /app
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk add --no-cache ca-certificates tzdata RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /build/bin/platform_linux_amd64 /app/platform COPY --from=builder /build/bin/platform_linux_amd64 /app/platform

View File

@@ -1,6 +1,10 @@
## TODO ## TODO
用户请求需要检查数据权限 proxy 的删除和更新,锁粒度应该有问题
最低价格 0.01
交易信息持久化
用反射实现环境变量解析,以简化函数签名 用反射实现环境变量解析,以简化函数签名
@@ -8,12 +12,8 @@
分离 task 的客户端支持多进程prefork 必要!) 分离 task 的客户端支持多进程prefork 必要!)
jsonb 类型转换问题,考虑一个高效的 any 到 struct 转换工具
慢速请求底层调用埋点监控 慢速请求底层调用埋点监控
数据库转模型文件
冷数据迁移方案 冷数据迁移方案
## 开发环境 ## 开发环境
@@ -47,13 +47,6 @@ jsonb 类型转换问题,考虑一个高效的 any 到 struct 转换工具
3. 异步回调事件,收到支付成功事件后自动完成订单 3. 异步回调事件,收到支付成功事件后自动完成订单
4. 用户退出支付界面,客户端主动发起关闭订单 4. 用户退出支付界面,客户端主动发起关闭订单
### 产品字典表
| 代码 | 产品 |
| ----- | ------------ |
| short | 短效动态代理 |
| long | 长效动态代理 |
### 节点分配与存储逻辑 ### 节点分配与存储逻辑
提取: 提取:

View File

@@ -66,6 +66,7 @@ func main() {
m.Inquiry{}, m.Inquiry{},
m.ProductDiscount{}, m.ProductDiscount{},
m.BalanceActivity{}, m.BalanceActivity{},
m.CouponUser{},
) )
g.Execute() g.Execute()
} }

View File

@@ -31,6 +31,15 @@ services:
ports: ports:
- "5433:5432" - "5433:5432"
asynqmon:
image: hibiken/asynqmon:latest
environment:
- REDIS_ADDR=redis:6379
ports:
- "9800:8080"
depends_on:
- redis
volumes: volumes:
postgres_data: postgres_data:
redis_data: redis_data:

2
pkg/env/env.go vendored
View File

@@ -24,7 +24,6 @@ var (
SessionAccessExpire = 60 * 60 * 2 // 访问令牌过期时间,单位秒。默认 2 小时 SessionAccessExpire = 60 * 60 * 2 // 访问令牌过期时间,单位秒。默认 2 小时
SessionRefreshExpire = 60 * 60 * 24 * 7 // 刷新令牌过期时间,单位秒。默认 7 天 SessionRefreshExpire = 60 * 60 * 24 * 7 // 刷新令牌过期时间,单位秒。默认 7 天
DebugHttpDump = false // 是否打印请求和响应的原始数据 DebugHttpDump = false // 是否打印请求和响应的原始数据
DebugExternalChange = true // 是否实际执行外部非幂等接口调用,在开发调试时可以关闭,避免对外部数据产生影响
DbHost = "localhost" DbHost = "localhost"
DbPort = "5432" DbPort = "5432"
@@ -106,7 +105,6 @@ func Init() {
errs = append(errs, parse(&SessionAccessExpire, "SESSION_ACCESS_EXPIRE", true, nil)) errs = append(errs, parse(&SessionAccessExpire, "SESSION_ACCESS_EXPIRE", true, nil))
errs = append(errs, parse(&SessionRefreshExpire, "SESSION_REFRESH_EXPIRE", true, nil)) errs = append(errs, parse(&SessionRefreshExpire, "SESSION_REFRESH_EXPIRE", true, nil))
errs = append(errs, parse(&DebugHttpDump, "DEBUG_HTTP_DUMP", true, nil)) errs = append(errs, parse(&DebugHttpDump, "DEBUG_HTTP_DUMP", true, nil))
errs = append(errs, parse(&DebugExternalChange, "DEBUG_EXTERNAL_CHANGE", true, nil))
errs = append(errs, parse(&DbHost, "DB_HOST", true, nil)) errs = append(errs, parse(&DbHost, "DB_HOST", true, nil))
errs = append(errs, parse(&DbPort, "DB_PORT", true, nil)) errs = append(errs, parse(&DbPort, "DB_PORT", true, nil))

View File

@@ -53,6 +53,18 @@ func X[T comparable](v T) *T {
return &v return &v
} }
// N 零值视为 nil
func N[T comparable](v *T) *T {
if v == nil {
return nil
}
var zero T
if *v == zero {
return nil
}
return v
}
// ==================== // ====================
// 数组 // 数组
// ==================== // ====================
@@ -110,3 +122,21 @@ func CombineErrors(errs []error) error {
} }
return combinedErr return combinedErr
} }
// ====================
// 业务
// ====================
func MaskPhone(phone string) string {
if len(phone) < 11 {
return phone
}
return phone[:3] + "****" + phone[7:]
}
func MaskIdNo(idNo string) string {
if len(idNo) < 18 {
return idNo
}
return idNo[:3] + "*********" + idNo[14:]
}

View File

@@ -1,21 +1,279 @@
-- ==================== -- ====================
-- region 填充数据 -- region 客户端
-- ==================== -- ====================
insert into client (type, spec, name, client_id, client_secret, redirect_uri) values (1, 3, 'web', 'web', '$2a$10$Ss12mXQgpYyo1CKIZ3URouDm.Lc2KcYJzsvEK2PTIXlv6fHQht45a', ''); insert into client (type, spec, name, client_id, client_secret, redirect_uri) values (1, 3, 'web', 'web', '$2a$10$Ss12mXQgpYyo1CKIZ3URouDm.Lc2KcYJzsvEK2PTIXlv6fHQht45a', '');
insert into client (type, spec, name, client_id, client_secret, redirect_uri) values (1, 3, 'admin', 'admin', '$2a$10$dlfvX5Uf3iVsUWgwlb0Wt.oYsw/OEXgS.Aior3yoT63Ju7ZSsJr/2', ''); insert into client (type, spec, name, client_id, client_secret, redirect_uri) values (1, 3, 'admin', 'admin', '$2a$10$dlfvX5Uf3iVsUWgwlb0Wt.oYsw/OEXgS.Aior3yoT63Ju7ZSsJr/2', '');
insert into product (code, name, description) values ('short', '短效动态', '短效动态'); -- ====================
insert into product (code, name, description) values ('long', '长效动态', '长效动态'); -- region 管理员
insert into product (code, name, description) values ('static', '长效静态', '长效静态'); -- ====================
insert into admin (username, password, name, lock) values ('admin', '', '超级管理员', true);
-- ====================
-- region 产品
-- ====================
delete from product where true;
insert into product (code, name, description, sort) values ('short', '短效动态', '短效动态', 1);
insert into product (code, name, description, sort) values ('long', '长效动态', '长效动态', 2);
insert into product (code, name, description, sort) values ('static', '长效静态', '长效静态', 3);
-- ====================
-- region 套餐
-- ====================
delete from product_sku where true;
insert into product_sku (product_id, code, name, price, price_min, sort) values
((select id from product where code = 'short' and deleted_at is null), 'mode=quota&live=3&expire=0', '短效动态包量 3 分钟', 10.00, 10.00, 1),
((select id from product where code = 'short' and deleted_at is null), 'mode=quota&live=5&expire=0', '短效动态包量 5 分钟', 10.00, 10.00, 2),
((select id from product where code = 'short' and deleted_at is null), 'mode=quota&live=10&expire=0', '短效动态包量 10 分钟', 10.00, 10.00, 3),
((select id from product where code = 'short' and deleted_at is null), 'mode=quota&live=15&expire=0', '短效动态包量 15 分钟', 10.00, 10.00, 4),
((select id from product where code = 'short' and deleted_at is null), 'mode=quota&live=30&expire=0', '短效动态包量 30 分钟', 10.00, 10.00, 5),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=3&expire=7', '短效动态包时 3 分钟 7 天', 10.00, 10.00, 6),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=5&expire=7', '短效动态包时 5 分钟 7 天', 10.00, 10.00, 7),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=10&expire=7', '短效动态包时 10 分钟 7 天', 10.00, 10.00, 8),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=15&expire=7', '短效动态包时 15 分钟 7 天', 10.00, 10.00, 9),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=30&expire=7', '短效动态包时 30 分钟 7 天', 10.00, 10.00, 10),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=3&expire=15', '短效动态包时 3 分钟 15 天', 10.00, 10.00, 11),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=5&expire=15', '短效动态包时 5 分钟 15 天', 10.00, 10.00, 12),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=10&expire=15', '短效动态包时 10 分钟 15 天', 10.00, 10.00, 13),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=15&expire=15', '短效动态包时 15 分钟 15 天', 10.00, 10.00, 14),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=30&expire=15', '短效动态包时 30 分钟 15 天', 10.00, 10.00, 15),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=3&expire=30', '短效动态包时 3 分钟 30 天', 10.00, 10.00, 16),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=5&expire=30', '短效动态包时 5 分钟 30 天', 10.00, 10.00, 17),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=10&expire=30', '短效动态包时 10 分钟 30 天', 10.00, 10.00, 18),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=15&expire=30', '短效动态包时 15 分钟 30 天', 10.00, 10.00, 19),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=30&expire=30', '短效动态包时 30 分钟 30 天', 10.00, 10.00, 20),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=3&expire=90', '短效动态包时 3 分钟 90 天', 10.00, 10.00, 21),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=5&expire=90', '短效动态包时 5 分钟 90 天', 10.00, 10.00, 22),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=10&expire=90', '短效动态包时 10 分钟 90 天', 10.00, 10.00, 23),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=15&expire=90', '短效动态包时 15 分钟 90 天', 10.00, 10.00, 24),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=30&expire=90', '短效动态包时 30 分钟 90 天', 10.00, 10.00, 25),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=3&expire=180', '短效动态包时 3 分钟 180 天', 10.00, 10.00, 26),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=5&expire=180', '短效动态包时 5 分钟 180 天', 10.00, 10.00, 27),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=10&expire=180', '短效动态包时 10 分钟 180 天', 10.00, 10.00, 28),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=15&expire=180', '短效动态包时 15 分钟 180 天', 10.00, 10.00, 29),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=30&expire=180', '短效动态包时 30 分钟 180 天', 10.00, 10.00, 30),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=3&expire=365', '短效动态包时 3 分钟 365 天', 10.00, 10.00, 31),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=5&expire=365', '短效动态包时 5 分钟 365 天', 10.00, 10.00, 32),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=10&expire=365', '短效动态包时 10 分钟 365 天', 10.00, 10.00, 33),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=15&expire=365', '短效动态包时 15 分钟 365 天', 10.00, 10.00, 34),
((select id from product where code = 'short' and deleted_at is null), 'mode=time&live=30&expire=365', '短效动态包时 30 分钟 365 天', 10.00, 10.00, 35)
;
insert into product_sku (product_id, code, name, price, price_min, sort) values
((select id from product where code = 'long' and deleted_at is null), 'mode=quota&live=60&expire=0', '长效动态包量 1 小时', 10.00, 10.00, 1),
((select id from product where code = 'long' and deleted_at is null), 'mode=quota&live=240&expire=0', '长效动态包量 4 小时', 10.00, 10.00, 2),
((select id from product where code = 'long' and deleted_at is null), 'mode=quota&live=480&expire=0', '长效动态包量 8 小时', 10.00, 10.00, 3),
((select id from product where code = 'long' and deleted_at is null), 'mode=quota&live=720&expire=0', '长效动态包量 12 小时', 10.00, 10.00, 4),
((select id from product where code = 'long' and deleted_at is null), 'mode=quota&live=1440&expire=0', '长效动态包量 24 小时', 10.00, 10.00, 5),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=60&expire=7', '长效动态包时 1 小时 7 天', 10.00, 10.00, 6),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=240&expire=7', '长效动态包时 4 小时 7 天', 10.00, 10.00, 7),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=480&expire=7', '长效动态包时 8 小时 7 天', 10.00, 10.00, 8),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=720&expire=7', '长效动态包时 12 小时 7 天', 10.00, 10.00, 9),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=1440&expire=7', '长效动态包时 24 小时 7 天', 10.00, 10.00, 10),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=60&expire=15', '长效动态包时 1 小时 15 天', 10.00, 10.00, 11),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=240&expire=15', '长效动态包时 4 小时 15 天', 10.00, 10.00, 12),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=480&expire=15', '长效动态包时 8 小时 15 天', 10.00, 10.00, 13),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=720&expire=15', '长效动态包时 12 小时 15 天', 10.00, 10.00, 14),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=1440&expire=15', '长效动态包时 24 小时 15 天', 10.00, 10.00, 15),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=60&expire=30', '长效动态包时 1 小时 30 天', 10.00, 10.00, 16),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=240&expire=30', '长效动态包时 4 小时 30 天', 10.00, 10.00, 17),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=480&expire=30', '长效动态包时 8 小时 30 天', 10.00, 10.00, 18),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=720&expire=30', '长效动态包时 12 小时 30 天', 10.00, 10.00, 19),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=1440&expire=30', '长效动态包时 24 小时 30 天', 10.00, 10.00, 20),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=60&expire=90', '长效动态包时 1 小时 90 天', 10.00, 10.00, 21),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=240&expire=90', '长效动态包时 4 小时 90 天', 10.00, 10.00, 22),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=480&expire=90', '长效动态包时 8 小时 90 天', 10.00, 10.00, 23),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=720&expire=90', '长效动态包时 12 小时 90 天', 10.00, 10.00, 24),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=1440&expire=90', '长效动态包时 24 小时 90 天', 10.00, 10.00, 25),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=60&expire=180', '长效动态包时 1 小时 180 天', 10.00, 10.00, 26),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=240&expire=180', '长效动态包时 4 小时 180 天', 10.00, 10.00, 27),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=480&expire=180', '长效动态包时 8 小时 180 天', 10.00, 10.00, 28),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=720&expire=180', '长效动态包时 12 小时 180 天', 10.00, 10.00, 29),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=1440&expire=180','长效动态包时 24 小时 180 天', 10.00, 10.00, 30),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=60&expire=365', '长效动态包时 1 小时 365 天', 10.00, 10.00, 31),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=240&expire=365', '长效动态包时 4 小时 365 天', 10.00, 10.00, 32),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=480&expire=365', '长效动态包时 8 小时 365 天', 10.00, 10.00, 33),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=720&expire=365', '长效动态包时 12 小时 365 天', 10.00, 10.00, 34),
((select id from product where code = 'long' and deleted_at is null), 'mode=time&live=1440&expire=365','长效动态包时 24 小时 365 天', 10.00, 10.00, 35)
;
-- ====================
-- region 权限
-- ====================
delete from permission where true; delete from permission where true;
insert into permission
(name, description) -- --------------------------
values -- level 1
('permission:read', '读取权限列表'), -- --------------------------
('permission:write', '写入权限'), insert into permission (name, description, sort) values
('admin-role:read', '读取管理员角色列表'), ('permission', '权限', 1),
('admin-role:write', '写入管理员角色') ('admin_role', '管理员角色', 2),
; ('admin', '管理员', 3),
('product', '产品', 4),
('product_sku', '产品套餐', 5),
('discount', '折扣', 6),
('resource', '用户套餐', 7),
('user', '用户', 8),
('coupon', '优惠券', 9),
('batch', '批次', 10),
('channel', 'IP', 11),
('trade', '交易', 12),
('bill', '账单', 13),
('balance_activity', '余额变动', 14),
('proxy', '代理', 15);
-- --------------------------
-- level 2
-- --------------------------
-- permission 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'permission' and deleted_at is null), 'permission:read', '读取权限列表', 1),
((select id from permission where name = 'permission' and deleted_at is null), 'permission:write', '写入权限', 2);
-- admin_role 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'admin_role' and deleted_at is null), 'admin_role:read', '读取管理员角色列表', 1),
((select id from permission where name = 'admin_role' and deleted_at is null), 'admin_role:write', '写入管理员角色', 2);
-- admin 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'admin' and deleted_at is null), 'admin:read', '读取管理员列表', 1),
((select id from permission where name = 'admin' and deleted_at is null), 'admin:write', '写入管理员', 2);
-- product 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'product' and deleted_at is null), 'product:read', '读取产品列表', 1),
((select id from permission where name = 'product' and deleted_at is null), 'product:write', '写入产品', 2);
-- product_sku 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'product_sku' and deleted_at is null), 'product_sku:read', '读取产品套餐列表', 1),
((select id from permission where name = 'product_sku' and deleted_at is null), 'product_sku:write', '写入产品套餐', 2);
-- discount 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'discount' and deleted_at is null), 'discount:read', '读取折扣列表', 1),
((select id from permission where name = 'discount' and deleted_at is null), 'discount:write', '写入折扣', 2);
-- resource 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'resource' and deleted_at is null), 'resource:read', '读取用户套餐列表', 1),
((select id from permission where name = 'resource' and deleted_at is null), 'resource:write', '写入用户套餐', 2),
((select id from permission where name = 'resource' and deleted_at is null), 'resource:short', '短效动态套餐', 3),
((select id from permission where name = 'resource' and deleted_at is null), 'resource:long', '长效动态套餐', 4);
-- user 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'user' and deleted_at is null), 'user:read', '读取用户列表', 1),
((select id from permission where name = 'user' and deleted_at is null), 'user:write', '写入用户', 2);
-- coupon 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'coupon' and deleted_at is null), 'coupon:read', '读取优惠券列表', 1),
((select id from permission where name = 'coupon' and deleted_at is null), 'coupon:write', '写入优惠券', 2);
-- batch 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'batch' and deleted_at is null), 'batch:read', '读取批次列表', 1),
((select id from permission where name = 'batch' and deleted_at is null), 'batch:write', '写入批次', 2);
-- channel 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'channel' and deleted_at is null), 'channel:read', '读取 IP 列表', 1),
((select id from permission where name = 'channel' and deleted_at is null), 'channel:write', '写入 IP', 2);
-- proxy 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'proxy' and deleted_at is null), 'proxy:read', '读取代理列表', 1),
((select id from permission where name = 'proxy' and deleted_at is null), 'proxy:write', '写入代理', 2);
-- trade 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'trade' and deleted_at is null), 'trade:read', '读取交易列表', 1),
((select id from permission where name = 'trade' and deleted_at is null), 'trade:write', '写入交易', 2);
-- bill 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'bill' and deleted_at is null), 'bill:read', '读取账单列表', 1),
((select id from permission where name = 'bill' and deleted_at is null), 'bill:write', '写入账单', 2);
-- balance_activity 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'balance_activity' and deleted_at is null), 'balance_activity:read', '读取余额变动列表', 1);
-- --------------------------
-- level 3
-- --------------------------
-- product_sku:write 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'product_sku:write' and deleted_at is null), 'product_sku:write:status', '更改产品套餐状态', 1);
-- proxy:write 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'proxy:write' and deleted_at is null), 'proxy:write:status', '更改代理状态', 1);
-- resource:short 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'resource:short' and deleted_at is null), 'resource:short:read', '读取用户短效动态套餐列表', 1);
-- resource:long 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'resource:long' and deleted_at is null), 'resource:long:read', '读取用户长效动态套餐列表', 1);
-- user:read 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'user:read' and deleted_at is null), 'user:read:one', '读取单个用户', 1),
((select id from permission where name = 'user:read' and deleted_at is null), 'user:read:not_bind', '读取未绑定管理员的用户列表', 2);
-- user:write 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'user:write' and deleted_at is null), 'user:write:balance', '写入用户余额', 1),
((select id from permission where name = 'user:write' and deleted_at is null), 'user:write:bind', '用户认领', 2);
-- batch:read 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'batch:read' and deleted_at is null), 'batch:read:of_user', '读取指定用户的批次列表', 1);
-- channel:read 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'channel:read' and deleted_at is null), 'channel:read:of_user', '读取指定用户的 IP 列表', 1);
-- trade:read 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'trade:read' and deleted_at is null), 'trade:read:of_user', '读取指定用户的交易列表', 1);
-- bill:read 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'bill:read' and deleted_at is null), 'bill:read:of_user', '读取指定用户的账单列表', 1);
-- balance_activity:read 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'balance_activity:read' and deleted_at is null), 'balance_activity:read:of_user', '读取指定用户的余额变动列表', 1);
-- --------------------------
-- level 4
-- --------------------------
-- user:write:balance 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'user:write:balance' and deleted_at is null), 'user:write:balance:inc', '增加用户余额', 1),
((select id from permission where name = 'user:write:balance' and deleted_at is null), 'user:write:balance:dec', '减少用户余额', 2);
-- resource:short:read 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'resource:short:read' and deleted_at is null), 'resource:short:read:of_user', '读取指定用户的短效动态套餐列表', 1);
-- resource:long:read 子权限
insert into permission (parent_id, name, description, sort) values
((select id from permission where name = 'resource:long:read' and deleted_at is null), 'resource:long:read:of_user', '读取指定用户的长效动态套餐列表', 1);
-- endregion -- endregion

View File

@@ -196,6 +196,7 @@ create table admin (
last_login timestamptz, last_login timestamptz,
last_login_ip inet, last_login_ip inet,
last_login_ua text, last_login_ua text,
lock bool not null default false,
created_at timestamptz default current_timestamp, created_at timestamptz default current_timestamp,
updated_at timestamptz default current_timestamp, updated_at timestamptz default current_timestamp,
deleted_at timestamptz deleted_at timestamptz
@@ -217,6 +218,7 @@ comment on column admin.status is '状态0-禁用1-正常';
comment on column admin.last_login is '最后登录时间'; comment on column admin.last_login is '最后登录时间';
comment on column admin.last_login_ip is '最后登录地址'; comment on column admin.last_login_ip is '最后登录地址';
comment on column admin.last_login_ua is '最后登录代理'; comment on column admin.last_login_ua is '最后登录代理';
comment on column admin.lock is '是否锁定编辑';
comment on column admin.created_at is '创建时间'; comment on column admin.created_at is '创建时间';
comment on column admin.updated_at is '更新时间'; comment on column admin.updated_at is '更新时间';
comment on column admin.deleted_at is '删除时间'; comment on column admin.deleted_at is '删除时间';
@@ -757,6 +759,10 @@ create table product_sku (
code text not null unique, code text not null unique,
name text not null, name text not null,
price decimal not null, price decimal not null,
price_min decimal not null,
status int not null default 1,
sort int not null default 0,
count_min int not null default 1,
created_at timestamptz default current_timestamp, created_at timestamptz default current_timestamp,
updated_at timestamptz default current_timestamp, updated_at timestamptz default current_timestamp,
deleted_at timestamptz deleted_at timestamptz
@@ -772,7 +778,10 @@ comment on column product_sku.product_id is '产品ID';
comment on column product_sku.discount_id is '折扣ID'; comment on column product_sku.discount_id is '折扣ID';
comment on column product_sku.code is 'SKU 代码:格式为 key=value,key=value,...其中key:value 是 SKU 的属性,多个属性用逗号分隔'; comment on column product_sku.code is 'SKU 代码:格式为 key=value,key=value,...其中key:value 是 SKU 的属性,多个属性用逗号分隔';
comment on column product_sku.name is 'SKU 可读名称'; comment on column product_sku.name is 'SKU 可读名称';
comment on column product_sku.price is '定价'; comment on column product_sku.price_min is '最低价格';
comment on column product_sku.status is 'SKU状态0-禁用1-正常';
comment on column product_sku.sort is '排序';
comment on column product_sku.count_min is '最小购买数量';
comment on column product_sku.created_at is '创建时间'; comment on column product_sku.created_at is '创建时间';
comment on column product_sku.updated_at is '更新时间'; comment on column product_sku.updated_at is '更新时间';
comment on column product_sku.deleted_at is '删除时间'; comment on column product_sku.deleted_at is '删除时间';
@@ -984,7 +993,7 @@ create table bill (
trade_id int, trade_id int,
resource_id int, resource_id int,
refund_id int, refund_id int,
coupon_id int, coupon_user_id int,
bill_no text not null, bill_no text not null,
info text, info text,
type int not null, type int not null,
@@ -999,7 +1008,7 @@ create index idx_bill_user_id on bill (user_id) where deleted_at is null;
create index idx_bill_trade_id on bill (trade_id) where deleted_at is null; create index idx_bill_trade_id on bill (trade_id) where deleted_at is null;
create index idx_bill_resource_id on bill (resource_id) where deleted_at is null; create index idx_bill_resource_id on bill (resource_id) where deleted_at is null;
create index idx_bill_refund_id on bill (refund_id) where deleted_at is null; create index idx_bill_refund_id on bill (refund_id) where deleted_at is null;
create index idx_bill_coupon_id on bill (coupon_id) where deleted_at is null; create index idx_bill_coupon_id on bill (coupon_user_id) where deleted_at is null;
create index idx_bill_created_at on bill (created_at) where deleted_at is null; create index idx_bill_created_at on bill (created_at) where deleted_at is null;
-- bill表字段注释 -- bill表字段注释
@@ -1009,6 +1018,7 @@ comment on column bill.user_id is '用户ID';
comment on column bill.trade_id is '订单ID'; comment on column bill.trade_id is '订单ID';
comment on column bill.resource_id is '套餐ID'; comment on column bill.resource_id is '套餐ID';
comment on column bill.refund_id is '退款ID'; comment on column bill.refund_id is '退款ID';
comment on column bill.coupon_user_id is '优惠券发放ID';
comment on column bill.bill_no is '易读账单号'; comment on column bill.bill_no is '易读账单号';
comment on column bill.info is '产品可读信息'; comment on column bill.info is '产品可读信息';
comment on column bill.type is '账单类型1-消费2-退款3-充值'; comment on column bill.type is '账单类型1-消费2-退款3-充值';
@@ -1052,35 +1062,58 @@ comment on column balance_activity.created_at is '创建时间';
drop table if exists coupon cascade; drop table if exists coupon cascade;
create table coupon ( create table coupon (
id int generated by default as identity primary key, id int generated by default as identity primary key,
user_id int, name text not null,
code text not null,
remark text,
amount decimal(12, 2) not null default 0, amount decimal(12, 2) not null default 0,
min_amount decimal(12, 2) not null default 0, min_amount decimal(12, 2) not null default 0,
status int not null default 0, count int not null default 0,
status int not null default 1,
expire_type int not null default 0,
expire_at timestamptz, expire_at timestamptz,
expire_in int,
created_at timestamptz default current_timestamp, created_at timestamptz default current_timestamp,
updated_at timestamptz default current_timestamp, updated_at timestamptz default current_timestamp,
deleted_at timestamptz deleted_at timestamptz
); );
create index idx_coupon_user_id on coupon (user_id) where deleted_at is null;
create index idx_coupon_code on coupon (code) where deleted_at is null;
create index idx_coupon_created_at on coupon (created_at) where deleted_at is null;
-- coupon表字段注释 -- coupon表字段注释
comment on table coupon is '优惠券表'; comment on table coupon is '优惠券表';
comment on column coupon.id is '优惠券ID'; comment on column coupon.id is '优惠券ID';
comment on column coupon.user_id is '用户ID'; comment on column coupon.name is '优惠券名称';
comment on column coupon.code is '优惠券代码';
comment on column coupon.remark is '优惠券备注';
comment on column coupon.amount is '优惠券金额'; comment on column coupon.amount is '优惠券金额';
comment on column coupon.min_amount is '最低消费金额'; comment on column coupon.min_amount is '最低消费金额';
comment on column coupon.status is '优惠券状态0-未使用1-已使用2-已过期'; comment on column coupon.count is '优惠券数量';
comment on column coupon.expire_at is '过期时间'; comment on column coupon.status is '优惠券状态0-禁用1-正常';
comment on column coupon.expire_type is '过期类型0-不过期1-固定日期2-相对日期(从发放时间算起)';
comment on column coupon.expire_at is '过期时间,固定日期必填';
comment on column coupon.expire_in is '过期时长(天),相对日期必填';
comment on column coupon.created_at is '创建时间'; comment on column coupon.created_at is '创建时间';
comment on column coupon.updated_at is '更新时间'; comment on column coupon.updated_at is '更新时间';
comment on column coupon.deleted_at is '删除时间'; comment on column coupon.deleted_at is '删除时间';
-- coupon_user 优惠券发放
drop table if exists coupon_user cascade;
create table coupon_user (
id int generated by default as identity primary key,
coupon_id int not null,
user_id int not null,
status int not null default 0,
expire_at timestamptz,
used_at timestamptz,
created_at timestamptz default current_timestamp
);
create index idx_coupon_user_coupon_id on coupon_user (coupon_id);
create index idx_coupon_user_user_id on coupon_user (user_id);
-- coupon_user表字段注释
comment on table coupon_user is '优惠券发放表';
comment on column coupon_user.id is '记录ID';
comment on column coupon_user.coupon_id is '优惠券ID';
comment on column coupon_user.user_id is '用户ID';
comment on column coupon_user.status is '使用状态0-未使用1-已使用';
comment on column coupon_user.expire_at is '过期时间';
comment on column coupon_user.used_at is '使用时间';
comment on column coupon_user.created_at is '创建时间';
-- endregion -- endregion
-- ==================== -- ====================
@@ -1185,11 +1218,13 @@ alter table bill
alter table bill alter table bill
add constraint fk_bill_refund_id foreign key (refund_id) references refund (id) on delete set null; add constraint fk_bill_refund_id foreign key (refund_id) references refund (id) on delete set null;
alter table bill alter table bill
add constraint fk_bill_coupon_id foreign key (coupon_id) references coupon (id) on delete set null; add constraint fk_bill_coupon_id foreign key (coupon_user_id) references coupon_user (id) on delete set null;
-- coupon表外键 -- coupon_user表外键
alter table coupon alter table coupon_user
add constraint fk_coupon_user_id foreign key (user_id) references "user" (id) on delete cascade; add constraint fk_coupon_user_user_id foreign key (user_id) references "user" (id) on delete cascade;
alter table coupon_user
add constraint fk_coupon_user_coupon_id foreign key (coupon_id) references coupon (id) on delete cascade;
-- product_sku表外键 -- product_sku表外键
alter table product_sku alter table product_sku

81
scripts/sql/update.sql Normal file
View File

@@ -0,0 +1,81 @@
-- ====================
-- region 套餐更新
-- ====================
-- --------------------------
-- 短效动态
-- --------------------------
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包量 3 分钟', price = 10.00, price_min = 10.00, sort = 1 where code = 'mode=quota&live=3&expire=0' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包量 5 分钟', price = 10.00, price_min = 10.00, sort = 2 where code = 'mode=quota&live=5&expire=0' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包量 10 分钟', price = 10.00, price_min = 10.00, sort = 3 where code = 'mode=quota&live=10&expire=0' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包量 15 分钟', price = 10.00, price_min = 10.00, sort = 4 where code = 'mode=quota&live=15&expire=0' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包量 30 分钟', price = 10.00, price_min = 10.00, sort = 5 where code = 'mode=quota&live=30&expire=0' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 3 分钟 7 天', price = 10.00, price_min = 10.00, sort = 6 where code = 'mode=time&live=3&expire=7' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 5 分钟 7 天', price = 10.00, price_min = 10.00, sort = 7 where code = 'mode=time&live=5&expire=7' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 10 分钟 7 天', price = 10.00, price_min = 10.00, sort = 8 where code = 'mode=time&live=10&expire=7' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 15 分钟 7 天', price = 10.00, price_min = 10.00, sort = 9 where code = 'mode=time&live=15&expire=7' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 30 分钟 7 天', price = 10.00, price_min = 10.00, sort = 10 where code = 'mode=time&live=30&expire=7' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 3 分钟 15 天', price = 10.00, price_min = 10.00, sort = 11 where code = 'mode=time&live=3&expire=15' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 5 分钟 15 天', price = 10.00, price_min = 10.00, sort = 12 where code = 'mode=time&live=5&expire=15' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 10 分钟 15 天', price = 10.00, price_min = 10.00, sort = 13 where code = 'mode=time&live=10&expire=15' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 15 分钟 15 天', price = 10.00, price_min = 10.00, sort = 14 where code = 'mode=time&live=15&expire=15' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 30 分钟 15 天', price = 10.00, price_min = 10.00, sort = 15 where code = 'mode=time&live=30&expire=15' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 3 分钟 30 天', price = 10.00, price_min = 10.00, sort = 16 where code = 'mode=time&live=3&expire=30' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 5 分钟 30 天', price = 10.00, price_min = 10.00, sort = 17 where code = 'mode=time&live=5&expire=30' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 10 分钟 30 天', price = 10.00, price_min = 10.00, sort = 18 where code = 'mode=time&live=10&expire=30' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 15 分钟 30 天', price = 10.00, price_min = 10.00, sort = 19 where code = 'mode=time&live=15&expire=30' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 30 分钟 30 天', price = 10.00, price_min = 10.00, sort = 20 where code = 'mode=time&live=30&expire=30' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 3 分钟 90 天', price = 10.00, price_min = 10.00, sort = 21 where code = 'mode=time&live=3&expire=90' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 5 分钟 90 天', price = 10.00, price_min = 10.00, sort = 22 where code = 'mode=time&live=5&expire=90' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 10 分钟 90 天', price = 10.00, price_min = 10.00, sort = 23 where code = 'mode=time&live=10&expire=90' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 15 分钟 90 天', price = 10.00, price_min = 10.00, sort = 24 where code = 'mode=time&live=15&expire=90' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 30 分钟 90 天', price = 10.00, price_min = 10.00, sort = 25 where code = 'mode=time&live=30&expire=90' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 3 分钟 180 天', price = 10.00, price_min = 10.00, sort = 26 where code = 'mode=time&live=3&expire=180' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 5 分钟 180 天', price = 10.00, price_min = 10.00, sort = 27 where code = 'mode=time&live=5&expire=180' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 10 分钟 180 天', price = 10.00, price_min = 10.00, sort = 28 where code = 'mode=time&live=10&expire=180' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 15 分钟 180 天', price = 10.00, price_min = 10.00, sort = 29 where code = 'mode=time&live=15&expire=180' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 30 分钟 180 天', price = 10.00, price_min = 10.00, sort = 30 where code = 'mode=time&live=30&expire=180' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 3 分钟 365 天', price = 10.00, price_min = 10.00, sort = 31 where code = 'mode=time&live=3&expire=365' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 5 分钟 365 天', price = 10.00, price_min = 10.00, sort = 32 where code = 'mode=time&live=5&expire=365' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 10 分钟 365 天', price = 10.00, price_min = 10.00, sort = 33 where code = 'mode=time&live=10&expire=365' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 15 分钟 365 天', price = 10.00, price_min = 10.00, sort = 34 where code = 'mode=time&live=15&expire=365' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'short' and deleted_at is null), name = '短效动态包时 30 分钟 365 天', price = 10.00, price_min = 10.00, sort = 35 where code = 'mode=time&live=30&expire=365' and deleted_at is null;
-- --------------------------
-- 长效动态
-- --------------------------
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包量 1 小时', price = 10.00, price_min = 10.00, sort = 1 where code = 'mode=quota&live=60&expire=0' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包量 4 小时', price = 10.00, price_min = 10.00, sort = 2 where code = 'mode=quota&live=240&expire=0' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包量 8 小时', price = 10.00, price_min = 10.00, sort = 3 where code = 'mode=quota&live=480&expire=0' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包量 12 小时', price = 10.00, price_min = 10.00, sort = 4 where code = 'mode=quota&live=720&expire=0' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包量 24 小时', price = 10.00, price_min = 10.00, sort = 5 where code = 'mode=quota&live=1440&expire=0' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 1 小时 7 天', price = 10.00, price_min = 10.00, sort = 6 where code = 'mode=time&live=60&expire=7' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 4 小时 7 天', price = 10.00, price_min = 10.00, sort = 7 where code = 'mode=time&live=240&expire=7' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 8 小时 7 天', price = 10.00, price_min = 10.00, sort = 8 where code = 'mode=time&live=480&expire=7' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 12 小时 7 天', price = 10.00, price_min = 10.00, sort = 9 where code = 'mode=time&live=720&expire=7' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 24 小时 7 天', price = 10.00, price_min = 10.00, sort = 10 where code = 'mode=time&live=1440&expire=7' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 1 小时 15 天', price = 10.00, price_min = 10.00, sort = 11 where code = 'mode=time&live=60&expire=15' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 4 小时 15 天', price = 10.00, price_min = 10.00, sort = 12 where code = 'mode=time&live=240&expire=15' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 8 小时 15 天', price = 10.00, price_min = 10.00, sort = 13 where code = 'mode=time&live=480&expire=15' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 12 小时 15 天', price = 10.00, price_min = 10.00, sort = 14 where code = 'mode=time&live=720&expire=15' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 24 小时 15 天', price = 10.00, price_min = 10.00, sort = 15 where code = 'mode=time&live=1440&expire=15' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 1 小时 30 天', price = 10.00, price_min = 10.00, sort = 16 where code = 'mode=time&live=60&expire=30' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 4 小时 30 天', price = 10.00, price_min = 10.00, sort = 17 where code = 'mode=time&live=240&expire=30' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 8 小时 30 天', price = 10.00, price_min = 10.00, sort = 18 where code = 'mode=time&live=480&expire=30' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 12 小时 30 天', price = 10.00, price_min = 10.00, sort = 19 where code = 'mode=time&live=720&expire=30' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 24 小时 30 天', price = 10.00, price_min = 10.00, sort = 20 where code = 'mode=time&live=1440&expire=30' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 1 小时 90 天', price = 10.00, price_min = 10.00, sort = 21 where code = 'mode=time&live=60&expire=90' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 4 小时 90 天', price = 10.00, price_min = 10.00, sort = 22 where code = 'mode=time&live=240&expire=90' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 8 小时 90 天', price = 10.00, price_min = 10.00, sort = 23 where code = 'mode=time&live=480&expire=90' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 12 小时 90 天', price = 10.00, price_min = 10.00, sort = 24 where code = 'mode=time&live=720&expire=90' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 24 小时 90 天', price = 10.00, price_min = 10.00, sort = 25 where code = 'mode=time&live=1440&expire=90' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 1 小时 180 天', price = 10.00, price_min = 10.00, sort = 26 where code = 'mode=time&live=60&expire=180' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 4 小时 180 天', price = 10.00, price_min = 10.00, sort = 27 where code = 'mode=time&live=240&expire=180' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 8 小时 180 天', price = 10.00, price_min = 10.00, sort = 28 where code = 'mode=time&live=480&expire=180' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 12 小时 180 天', price = 10.00, price_min = 10.00, sort = 29 where code = 'mode=time&live=720&expire=180' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 24 小时 180 天', price = 10.00, price_min = 10.00, sort = 30 where code = 'mode=time&live=1440&expire=180' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 1 小时 365 天', price = 10.00, price_min = 10.00, sort = 31 where code = 'mode=time&live=60&expire=365' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 4 小时 365 天', price = 10.00, price_min = 10.00, sort = 32 where code = 'mode=time&live=240&expire=365' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 8 小时 365 天', price = 10.00, price_min = 10.00, sort = 33 where code = 'mode=time&live=480&expire=365' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 12 小时 365 天', price = 10.00, price_min = 10.00, sort = 34 where code = 'mode=time&live=720&expire=365' and deleted_at is null;
update product_sku set product_id = (select id from product where code = 'long' and deleted_at is null), name = '长效动态包时 24 小时 365 天', price = 10.00, price_min = 10.00, sort = 35 where code = 'mode=time&live=1440&expire=365' and deleted_at is null;

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"log/slog" "log/slog"
"platform/pkg/u"
"platform/web/core" "platform/web/core"
m "platform/web/models" m "platform/web/models"
q "platform/web/queries" q "platform/web/queries"
@@ -51,7 +50,6 @@ func authUser(loginType PwdLoginType, username, password string) (user *m.User,
if user == nil { if user == nil {
user = &m.User{ user = &m.User{
Phone: username, Phone: username,
Username: u.P(username),
Status: m.UserStatusEnabled, Status: m.UserStatusEnabled,
} }
} }
@@ -79,7 +77,7 @@ func authUser(loginType PwdLoginType, username, password string) (user *m.User,
func authUserBySms(tx *q.Query, username, code string) (*m.User, error) { func authUserBySms(tx *q.Query, username, code string) (*m.User, error) {
// 验证验证码 // 验证验证码
err := s.Verifier.VerifySms(context.Background(), username, code) err := s.Verifier.VerifySms(context.Background(), username, code, s.VerifierSmsPurposeLogin)
if err != nil { if err != nil {
return nil, core.NewBizErr("短信认证失败", err) return nil, core.NewBizErr("短信认证失败", err)
} }
@@ -169,6 +167,9 @@ func adminScopes(admin *m.Admin) ([]string, error) {
scopeNames := make([]string, 0, len(scopes)) scopeNames := make([]string, 0, len(scopes))
for _, scope := range scopes { for _, scope := range scopes {
if scope.Name == "" {
continue
}
scopeNames = append(scopeNames, scope.Name) scopeNames = append(scopeNames, scope.Name)
} }
return scopeNames, nil return scopeNames, nil

View File

@@ -356,6 +356,11 @@ func authPassword(c *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.Time) (*m
return nil, err return nil, err
} }
// 非锁定管理员,不允许为空权限
if !admin.Lock && (len(scopes) == 0) {
return nil, ErrAuthorizeInvalidScope // 没有配置权限
}
// 更新管理员登录时间 // 更新管理员登录时间
admin.LastLogin = u.P(time.Now()) admin.LastLogin = u.P(time.Now())
admin.LastLoginIP = ip admin.LastLoginIP = ip
@@ -532,10 +537,10 @@ func introspectUser(ctx *fiber.Ctx, authCtx *AuthCtx) error {
// 掩码敏感信息 // 掩码敏感信息
if profile.Phone != "" { if profile.Phone != "" {
profile.Phone = maskPhone(profile.Phone) profile.Phone = u.MaskPhone(profile.Phone)
} }
if profile.IDNo != nil && *profile.IDNo != "" { if profile.IDNo != nil && *profile.IDNo != "" {
profile.IDNo = u.P(maskIdNo(*profile.IDNo)) profile.IDNo = u.P(u.MaskIdNo(*profile.IDNo))
} }
return ctx.JSON(struct { return ctx.JSON(struct {
@@ -574,20 +579,6 @@ func introspectAdmin(ctx *fiber.Ctx, authCtx *AuthCtx) error {
}{profile, list}) }{profile, list})
} }
func maskPhone(phone string) string {
if len(phone) < 11 {
return phone
}
return phone[:3] + "****" + phone[7:]
}
func maskIdNo(idNo string) string {
if len(idNo) < 18 {
return idNo
}
return idNo[:3] + "*********" + idNo[14:]
}
type CodeContext struct { type CodeContext struct {
UserID int32 `json:"user_id"` UserID int32 `json:"user_id"`
ClientID int32 `json:"client_id"` ClientID int32 `json:"client_id"`

View File

@@ -12,9 +12,9 @@ type IModel interface {
} }
type Model struct { type Model struct {
ID int32 `json:"id" gorm:"column:id;primaryKey"` ID int32 `json:"id,omitzero" gorm:"column:id;primaryKey"`
CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` CreatedAt time.Time `json:"created_at,omitzero" gorm:"column:created_at"`
UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` UpdatedAt time.Time `json:"updated_at,omitzero" gorm:"column:updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"column:deleted_at"` DeletedAt gorm.DeletedAt `json:"-" gorm:"column:deleted_at"`
} }

View File

@@ -20,6 +20,7 @@ const (
ScopeProductSku = string("product_sku") // 产品套餐 ScopeProductSku = string("product_sku") // 产品套餐
ScopeProductSkuRead = string("product_sku:read") // 读取产品套餐列表 ScopeProductSkuRead = string("product_sku:read") // 读取产品套餐列表
ScopeProductSkuWrite = string("product_sku:write") // 写入产品套餐 ScopeProductSkuWrite = string("product_sku:write") // 写入产品套餐
ScopeProductSkuWriteStatus = string("product_sku:write:status") // 更改产品套餐状态
ScopeDiscount = string("discount") // 折扣 ScopeDiscount = string("discount") // 折扣
ScopeDiscountRead = string("discount:read") // 读取折扣列表 ScopeDiscountRead = string("discount:read") // 读取折扣列表
@@ -29,11 +30,22 @@ const (
ScopeResourceRead = string("resource:read") // 读取用户套餐列表 ScopeResourceRead = string("resource:read") // 读取用户套餐列表
ScopeResourceWrite = string("resource:write") // 写入用户套餐 ScopeResourceWrite = string("resource:write") // 写入用户套餐
ScopeResourceShort = string("resource:short") // 短效动态套餐
ScopeResourceShortRead = string("resource:short:read") // 读取用户短效动态套餐列表
ScopeResourceShortReadOfUser = string("resource:short:read:of_user") // 读取指定用户的短效动态套餐列表
ScopeResourceLong = string("resource:long") // 长效动态套餐
ScopeResourceLongRead = string("resource:long:read") // 读取用户长效动态套餐列表
ScopeResourceLongReadOfUser = string("resource:long:read:of_user") // 读取指定用户的长效动态套餐列表
ScopeUser = string("user") // 用户 ScopeUser = string("user") // 用户
ScopeUserRead = string("user:read") // 读取用户列表 ScopeUserRead = string("user:read") // 读取用户列表
ScopeUserReadOne = string("user:read:one") // 读取单个用户 ScopeUserReadOne = string("user:read:one") // 读取单个用户
ScopeUserReadNotBind = string("user:read:not_bind") // 读取未绑定管理员的用户列表
ScopeUserWrite = string("user:write") // 写入用户 ScopeUserWrite = string("user:write") // 写入用户
ScopeUserWriteBalance = string("user:write:balance") // 写入用户余额 ScopeUserWriteBalance = string("user:write:balance") // 写入用户余额
ScopeUserWriteBalanceInc = string("user:write:balance:inc") // 增加用户余额
ScopeUserWriteBalanceDec = string("user:write:balance:dec") // 减少用户余额
ScopeUserWriteBind = string("user:write:bind") // 用户认领 ScopeUserWriteBind = string("user:write:bind") // 用户认领
ScopeCoupon = string("coupon") // 优惠券 ScopeCoupon = string("coupon") // 优惠券
@@ -42,17 +54,31 @@ const (
ScopeBatch = string("batch") // 批次 ScopeBatch = string("batch") // 批次
ScopeBatchRead = string("batch:read") // 读取批次列表 ScopeBatchRead = string("batch:read") // 读取批次列表
ScopeBatchReadOfUser = string("batch:read:of_user") // 读取指定用户的批次列表
ScopeBatchWrite = string("batch:write") // 写入批次 ScopeBatchWrite = string("batch:write") // 写入批次
ScopeChannel = string("channel") // IP ScopeChannel = string("channel") // IP
ScopeChannelRead = string("channel:read") // 读取 IP 列表 ScopeChannelRead = string("channel:read") // 读取 IP 列表
ScopeChannelReadOfUser = string("channel:read:of_user") // 读取指定用户的 IP 列表
ScopeChannelWrite = string("channel:write") // 写入 IP ScopeChannelWrite = string("channel:write") // 写入 IP
ScopeProxy = string("proxy") // 代理
ScopeProxyRead = string("proxy:read") // 读取代理列表
ScopeProxyWrite = string("proxy:write") // 写入代理
ScopeProxyWriteStatus = string("proxy:write:status") // 更改代理状态
ScopeTrade = string("trade") // 交易 ScopeTrade = string("trade") // 交易
ScopeTradeRead = string("trade:read") // 读取交易列表 ScopeTradeRead = string("trade:read") // 读取交易列表
ScopeTradeReadOfUser = string("trade:read:of_user") // 读取指定用户的交易列表
ScopeTradeWrite = string("trade:write") // 写入交易 ScopeTradeWrite = string("trade:write") // 写入交易
ScopeTradeWriteComplete = string("trade:write:complete") // 完成交易
ScopeBill = string("bill") // 账单 ScopeBill = string("bill") // 账单
ScopeBillRead = string("bill:read") // 读取账单列表 ScopeBillRead = string("bill:read") // 读取账单列表
ScopeBillReadOfUser = string("bill:read:of_user") // 读取指定用户的账单列表
ScopeBillWrite = string("bill:write") // 写入账单 ScopeBillWrite = string("bill:write") // 写入账单
ScopeBalanceActivity = string("balance_activity") // 余额变动
ScopeBalanceActivityRead = string("balance_activity:read") // 读取余额变动列表
ScopeBalanceActivityReadOfUser = string("balance_activity:read:of_user") // 读取指定用户的余额变动列表
) )

View File

@@ -15,12 +15,12 @@ func PageAdminByAdmin(c *fiber.Ctx) error {
return err return err
} }
var req PageAdminsReq var req core.PageReq
if err := g.Validator.ParseBody(c, &req); err != nil { if err := g.Validator.ParseBody(c, &req); err != nil {
return err return err
} }
list, total, err := s.Admin.PageAdmins(req.PageReq) list, total, err := s.Admin.Page(req)
if err != nil { if err != nil {
return err return err
} }
@@ -33,10 +33,6 @@ func PageAdminByAdmin(c *fiber.Ctx) error {
}) })
} }
type PageAdminsReq struct {
core.PageReq
}
func AllAdminByAdmin(c *fiber.Ctx) error { func AllAdminByAdmin(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeAdminRead) _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeAdminRead)
if err != nil { if err != nil {
@@ -62,7 +58,7 @@ func CreateAdmin(c *fiber.Ctx) error {
return err return err
} }
if err := s.Admin.CreateAdmin(&req); err != nil { if err := s.Admin.Create(&req); err != nil {
return err return err
} }
@@ -80,7 +76,7 @@ func UpdateAdmin(c *fiber.Ctx) error {
return err return err
} }
if err := s.Admin.UpdateAdmin(&req); err != nil { if err := s.Admin.Update(&req); err != nil {
return err return err
} }
@@ -98,7 +94,7 @@ func RemoveAdmin(c *fiber.Ctx) error {
return err return err
} }
if err := s.Admin.RemoveAdmin(req.Id); err != nil { if err := s.Admin.Remove(req.Id); err != nil {
return err return err
} }

View File

@@ -0,0 +1,137 @@
package handlers
import (
"platform/pkg/u"
"platform/web/auth"
"platform/web/core"
g "platform/web/globals"
q "platform/web/queries"
"time"
"github.com/gofiber/fiber/v2"
)
// PageBalanceActivityByAdmin 分页查询所有余额变动记录
func PageBalanceActivityByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeBalanceActivityRead)
if err != nil {
return err
}
// 解析请求参数
req := new(PageBalanceActivityByAdminReq)
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
// 构造查询条件
do := q.BalanceActivity.Where()
if req.UserPhone != nil {
do = do.Where(q.User.As("User").Phone.Eq(*req.UserPhone))
}
if req.BillNo != nil {
do = do.Where(q.Bill.As("Bill").BillNo.Eq(*req.BillNo))
}
if req.CreatedAtStart != nil {
t := u.DateHead(*req.CreatedAtStart)
do = do.Where(q.BalanceActivity.CreatedAt.Gte(t))
}
if req.CreatedAtEnd != nil {
t := u.DateTail(*req.CreatedAtEnd)
do = do.Where(q.BalanceActivity.CreatedAt.Lte(t))
}
// 查询余额变动列表
list, total, err := q.BalanceActivity.Debug().
Joins(q.BalanceActivity.User, q.BalanceActivity.Admin, q.BalanceActivity.Bill).
Select(
q.BalanceActivity.ALL,
q.User.As("User").Phone.As("User__phone"),
q.User.As("User").Name.As("User__name"),
q.Admin.As("Admin").Name.As("Admin__name"),
q.Bill.As("Bill").BillNo.As("Bill__bill_no"),
).
Where(do).
Order(q.BalanceActivity.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return core.NewBizErr("获取数据失败", err)
}
// 返回结果
return c.JSON(core.PageResp{
List: list,
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
})
}
type PageBalanceActivityByAdminReq struct {
core.PageReq
UserPhone *string `json:"user_phone,omitempty"`
BillNo *string `json:"bill_no,omitempty"`
CreatedAtStart *time.Time `json:"created_at_start,omitempty"`
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
}
// PageBalanceActivityOfUserByAdmin 分页查询指定用户的余额变动记录
func PageBalanceActivityOfUserByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeBalanceActivityReadOfUser)
if err != nil {
return err
}
// 解析请求参数
req := new(PageBalanceActivityOfUserByAdminReq)
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
// 构造查询条件
do := q.BalanceActivity.Where(q.BalanceActivity.UserID.Eq(req.UserID))
if req.BillNo != nil {
do = do.Where(q.Bill.As("Bill").BillNo.Eq(*req.BillNo))
}
if req.CreatedAtStart != nil {
t := u.DateHead(*req.CreatedAtStart)
do = do.Where(q.BalanceActivity.CreatedAt.Gte(t))
}
if req.CreatedAtEnd != nil {
t := u.DateTail(*req.CreatedAtEnd)
do = do.Where(q.BalanceActivity.CreatedAt.Lte(t))
}
// 查询余额变动列表
list, total, err := q.BalanceActivity.
Joins(q.BalanceActivity.Admin, q.BalanceActivity.Bill).
Select(
q.BalanceActivity.ALL,
q.Admin.As("Admin").Name.As("Admin__name"),
q.Bill.As("Bill").BillNo.As("Bill__bill_no"),
).
Where(do).
Order(q.BalanceActivity.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return core.NewBizErr("获取数据失败", err)
}
// 返回结果
return c.JSON(core.PageResp{
List: list,
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
})
}
type PageBalanceActivityOfUserByAdminReq struct {
core.PageReq
UserID int32 `json:"user_id" validate:"required"`
BillNo *string `json:"bill_no,omitempty"`
CreatedAtStart *time.Time `json:"created_at_start,omitempty"`
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
}

View File

@@ -71,10 +71,10 @@ func PageBatchByAdmin(c *fiber.Ctx) error {
do := q.LogsUserUsage.Where() do := q.LogsUserUsage.Where()
if req.UserPhone != nil { if req.UserPhone != nil {
do = do.Where(q.User.Phone.Eq(*req.UserPhone)) do = do.Where(q.User.As("User").Phone.Eq(*req.UserPhone))
} }
if req.ResourceNo != nil { if req.ResourceNo != nil {
do = do.Where(q.Resource.ResourceNo.Eq(*req.ResourceNo)) do = do.Where(q.Resource.As("Resource").ResourceNo.Eq(*req.ResourceNo))
} }
if req.BatchNo != nil { if req.BatchNo != nil {
do = do.Where(q.LogsUserUsage.BatchNo.Eq(*req.BatchNo)) do = do.Where(q.LogsUserUsage.BatchNo.Eq(*req.BatchNo))
@@ -128,3 +128,75 @@ type PageBatchByAdminReq struct {
CreatedAtStart *time.Time `json:"created_at_start"` CreatedAtStart *time.Time `json:"created_at_start"`
CreatedAtEnd *time.Time `json:"created_at_end"` CreatedAtEnd *time.Time `json:"created_at_end"`
} }
// PageBatchOfUserByAdmin 分页查询指定用户的提取记录
func PageBatchOfUserByAdmin(ctx *fiber.Ctx) error {
_, err := auth.GetAuthCtx(ctx).PermitAdmin(core.ScopeBatchReadOfUser)
if err != nil {
return err
}
var req PageBatchOfUserByAdminReq
if err = g.Validator.ParseBody(ctx, &req); err != nil {
return err
}
do := q.LogsUserUsage.Where(q.LogsUserUsage.UserID.Eq(req.UserID))
if req.ResourceNo != nil {
do = do.Where(q.Resource.As("Resource").ResourceNo.Eq(*req.ResourceNo))
}
if req.BatchNo != nil {
do = do.Where(q.LogsUserUsage.BatchNo.Eq(*req.BatchNo))
}
if req.Prov != nil {
do = do.Where(q.LogsUserUsage.Prov.Eq(*req.Prov))
}
if req.City != nil {
do = do.Where(q.LogsUserUsage.City.Eq(*req.City))
}
if req.Isp != nil {
do = do.Where(q.LogsUserUsage.ISP.Eq(*req.Isp))
}
if req.CreatedAtStart != nil {
t := u.DateHead(*req.CreatedAtStart)
do = do.Where(q.LogsUserUsage.Time.Gte(t))
}
if req.CreatedAtEnd != nil {
t := u.DateTail(*req.CreatedAtEnd)
do = do.Where(q.LogsUserUsage.Time.Lte(t))
}
list, total, err := q.LogsUserUsage.
Joins(q.LogsUserUsage.User, q.LogsUserUsage.Resource).
Select(
q.LogsUserUsage.ALL,
q.User.As("User").Phone.As("User__phone"),
q.User.As("User").Name.As("User__name"),
q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"),
).
Where(do).
Order(q.LogsUserUsage.Time.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return ctx.JSON(core.NewBizErr("获取数据失败", err))
}
return ctx.JSON(c.PageResp{
List: list,
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
})
}
type PageBatchOfUserByAdminReq struct {
c.PageReq
UserID int32 `json:"user_id" validate:"required"`
ResourceNo *string `json:"resource_no"`
BatchNo *string `json:"batch_no"`
Prov *string `json:"prov"`
City *string `json:"city"`
Isp *string `json:"isp"`
CreatedAtStart *time.Time `json:"created_at_start"`
CreatedAtEnd *time.Time `json:"created_at_end"`
}

View File

@@ -101,6 +101,90 @@ type PageBillByAdminReq struct {
SkuCode *string `json:"sku_code,omitempty"` SkuCode *string `json:"sku_code,omitempty"`
} }
// PageBillOfUserByAdmin 分页查询指定用户账单
func PageBillOfUserByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeBillReadOfUser)
if err != nil {
return err
}
// 解析请求参数
req := new(PageBillOfUserByAdminReq)
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
// 构造查询条件
do := q.Bill.Where(q.Bill.UserID.Eq(req.UserID))
if req.TradeInnerNo != nil {
do = do.Where(q.Trade.As("Trade").InnerNo.Eq(*req.TradeInnerNo))
}
if req.ResourceNo != nil {
do = do.Where(q.Resource.As("Resource").ResourceNo.Eq(*req.ResourceNo))
}
if req.BillNo != nil {
do = do.Where(q.Bill.BillNo.Eq(*req.BillNo))
}
if req.CreatedAtStart != nil {
time := u.DateHead(*req.CreatedAtStart)
do = do.Where(q.Bill.CreatedAt.Gte(time))
}
if req.CreatedAtEnd != nil {
time := u.DateHead(*req.CreatedAtEnd)
do = do.Where(q.Bill.CreatedAt.Lte(time))
}
if req.ProductCode != nil {
do = do.Where(q.Resource.As("Resource").Code.Eq(*req.ProductCode))
}
if req.SkuCode != nil {
do = do.Where(q.Bill.
Where(q.ResourceShort.As("Resource__Short").Code.Eq(*req.SkuCode)).
Or(q.ResourceLong.As("Resource__Long").Code.Eq(*req.SkuCode)))
}
// 查询账单列表
list, total, err := q.Bill.
Joins(
q.Bill.Resource,
q.Bill.Trade,
q.Bill.Resource.Short,
q.Bill.Resource.Long,
).
Select(
q.Bill.ALL,
q.Trade.As("Trade").InnerNo.As("Trade__inner_no"),
q.Trade.As("Trade").Acquirer.As("Trade__acquirer"),
q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"),
).
Where(do).
Order(q.Bill.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return err
}
// 返回结果
return c.JSON(core.PageResp{
List: list,
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
})
}
type PageBillOfUserByAdminReq struct {
core.PageReq
UserID int32 `json:"user_id" validate:"required"`
TradeInnerNo *string `json:"trade_inner_no,omitempty"`
ResourceNo *string `json:"resource_no,omitempty"`
BillNo *string `json:"bill_no,omitempty"`
CreatedAtStart *time.Time `json:"created_at_start,omitempty"`
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
ProductCode *string `json:"product_code,omitempty"`
SkuCode *string `json:"sku_code,omitempty"`
}
// ListBill 获取账单列表 // ListBill 获取账单列表
func ListBill(c *fiber.Ctx) error { func ListBill(c *fiber.Ctx) error {
// 检查权限 // 检查权限

View File

@@ -201,7 +201,7 @@ func CreateChannel(c *fiber.Ctx) error {
req.AuthType == s.ChannelAuthTypeIp, req.AuthType == s.ChannelAuthTypeIp,
req.AuthType == s.ChannelAuthTypePass, req.AuthType == s.ChannelAuthTypePass,
req.Count, req.Count,
s.EdgeFilter{ &s.EdgeFilter{
Isp: isp, Isp: isp,
Prov: req.Prov, Prov: req.Prov,
City: req.City, City: req.City,
@@ -271,3 +271,76 @@ func RemoveChannels(c *fiber.Ctx) error {
type RemoveChannelsReq struct { type RemoveChannelsReq struct {
Batch string `json:"batch" validate:"required"` Batch string `json:"batch" validate:"required"`
} }
// PageChannelOfUserByAdmin 分页查询指定用户的通道
func PageChannelOfUserByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeChannelReadOfUser)
if err != nil {
return err
}
// 解析请求参数
var req PageChannelOfUserByAdminReq
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
// 构建查询条件
do := q.Channel.Where(q.Channel.UserID.Eq(req.UserID))
if req.ResourceNo != nil {
do = do.Where(q.Resource.As("Resource").ResourceNo.Eq(*req.ResourceNo))
}
if req.BatchNo != nil {
do = do.Where(q.Channel.BatchNo.Eq(*req.BatchNo))
}
if req.ProxyHost != nil {
do = do.Where(q.Channel.Host.Eq(*req.ProxyHost))
}
if req.ProxyPort != nil {
do = do.Where(q.Channel.Port.Eq(*req.ProxyPort))
}
if req.ExpiredAtStart != nil {
t := u.DateHead(*req.ExpiredAtStart)
do = do.Where(q.Channel.ExpiredAt.Gte(t))
}
if req.ExpiredAtEnd != nil {
t := u.DateHead(*req.ExpiredAtEnd)
do = do.Where(q.Channel.ExpiredAt.Lte(t))
}
// 查询通道列表
list, total, err := q.Channel.
Joins(q.Channel.User, q.Channel.Resource).
Select(
q.Channel.ALL,
q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"),
q.User.As("User").Phone.As("User__phone"),
q.User.As("User").Name.As("User__name"),
).
Where(do).
Order(q.Channel.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return err
}
// 返回结果
return c.JSON(core.PageResp{
List: list,
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
})
}
type PageChannelOfUserByAdminReq struct {
core.PageReq
UserID int32 `json:"user_id" validate:"required"`
ResourceNo *string `json:"resource_no"`
BatchNo *string `json:"batch_no"`
ProxyHost *string `json:"proxy_host"`
ProxyPort *uint16 `json:"proxy_port"`
ExpiredAtStart *time.Time `json:"expired_at_start"`
ExpiredAtEnd *time.Time `json:"expired_at_end"`
}

View File

@@ -34,6 +34,15 @@ func AllProductByAdmin(c *fiber.Ctx) error {
type AllProductsByAdminReq struct { type AllProductsByAdminReq struct {
} }
func AllProduct(c *fiber.Ctx) error {
infos, err := s.Product.AllProductSaleInfos()
if err != nil {
return err
}
return c.JSON(infos)
}
func CreateProduct(c *fiber.Ctx) error { func CreateProduct(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProductWrite) _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProductWrite)
if err != nil { if err != nil {
@@ -181,6 +190,32 @@ func UpdateProductSku(c *fiber.Ctx) error {
return nil return nil
} }
func UpdateProductStatusSku(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProductSkuWrite)
if err != nil {
return err
}
type Params struct {
ID int32 `json:"id"`
Status int32 `json:"status"`
}
var req Params
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
err = s.ProductSku.Update(s.UpdateProductSkuData{
ID: req.ID,
Status: &req.Status,
})
if err != nil {
return err
}
return nil
}
func BatchUpdateProductSkuDiscount(c *fiber.Ctx) error { func BatchUpdateProductSkuDiscount(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProductSkuWrite) _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProductSkuWrite)
if err != nil { if err != nil {

View File

@@ -1,61 +1,123 @@
package handlers package handlers
import ( import (
"net/netip"
"platform/pkg/env"
"platform/web/auth" "platform/web/auth"
"platform/web/core" "platform/web/core"
"platform/web/globals" g "platform/web/globals"
s "platform/web/services" s "platform/web/services"
"time" "time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
func DebugRegisterProxyBaiYin(c *fiber.Ctx) error { func PageProxyByAdmin(c *fiber.Ctx) error {
if env.RunMode != env.RunModeDev { _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProxyRead)
return fiber.ErrNotFound
}
err := s.Proxy.RegisterBaiyin("1a:2b:3c:4d:5e:6f", netip.AddrFrom4([4]byte{127, 0, 0, 1}), "test", "test")
if err != nil {
return core.NewServErr("注册失败", err)
}
return nil
}
// 注册白银代理网关
func ProxyRegisterBaiYin(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitOfficialClient()
if err != nil { if err != nil {
return err return err
} }
req := new(RegisterProxyBaiyinReq) var req core.PageReq
err = globals.Validator.ParseBody(c, req) if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
list, total, err := s.Proxy.Page(req)
if err != nil { if err != nil {
return err return err
} }
addr, err := netip.ParseAddr(req.IP) return c.JSON(core.PageResp{
if err != nil { List: list,
return core.NewServErr("IP地址格式错误", err) Total: int(total),
} Page: req.GetPage(),
Size: req.GetSize(),
err = s.Proxy.RegisterBaiyin(req.Name, addr, req.Username, req.Password) })
if err != nil {
return core.NewServErr("注册失败", err)
}
return nil
} }
type RegisterProxyBaiyinReq struct { func AllProxyByAdmin(c *fiber.Ctx) error {
Name string `json:"name" validate:"required"` _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProxyRead)
IP string `json:"ip" validate:"required"` if err != nil {
Username string `json:"username" validate:"required"` return err
Password string `json:"password" validate:"required"` }
list, err := s.Proxy.All()
if err != nil {
return err
}
return c.JSON(list)
}
func CreateProxy(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProxyWrite)
if err != nil {
return err
}
var req s.CreateProxy
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
if err := s.Proxy.Create(&req); err != nil {
return err
}
return c.JSON(nil)
}
func UpdateProxy(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProxyWrite)
if err != nil {
return err
}
var req s.UpdateProxy
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
if err := s.Proxy.Update(&req); err != nil {
return err
}
return c.JSON(nil)
}
func UpdateProxyStatus(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProxyWriteStatus)
if err != nil {
return err
}
var req s.UpdateProxyStatus
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
if err := s.Proxy.UpdateStatus(&req); err != nil {
return err
}
return c.JSON(nil)
}
func RemoveProxy(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeProxyWrite)
if err != nil {
return err
}
var req core.IdReq
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
if err := s.Proxy.Remove(req.Id); err != nil {
return err
}
return c.JSON(nil)
} }
// region 报告上线 // region 报告上线

View File

@@ -209,7 +209,7 @@ type PageResourceLongReq struct {
// PageResourceShortByAdmin 分页查询全部短效套餐 // PageResourceShortByAdmin 分页查询全部短效套餐
func PageResourceShortByAdmin(c *fiber.Ctx) error { func PageResourceShortByAdmin(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeResourceRead) _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeResourceShortRead)
if err != nil { if err != nil {
return err return err
} }
@@ -303,7 +303,7 @@ type PageResourceShortByAdminReq struct {
// PageResourceLongByAdmin 分页查询全部长效套餐 // PageResourceLongByAdmin 分页查询全部长效套餐
func PageResourceLongByAdmin(c *fiber.Ctx) error { func PageResourceLongByAdmin(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeResourceRead) _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeResourceLongRead)
if err != nil { if err != nil {
return err return err
} }
@@ -393,6 +393,148 @@ type PageResourceLongByAdminReq struct {
Expired *bool `json:"expired" form:"expired"` Expired *bool `json:"expired" form:"expired"`
} }
// PageResourceShortOfUserByAdmin 分页查询指定用户的短效套餐
func PageResourceShortOfUserByAdmin(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeResourceShortReadOfUser)
if err != nil {
return err
}
var req PageResourceShortOfUserByAdminReq
if err = g.Validator.ParseBody(c, &req); err != nil {
return err
}
do := q.Resource.Where(q.Resource.UserID.Eq(req.UserID))
if req.ResourceNo != nil {
do = do.Where(q.Resource.ResourceNo.Eq(*req.ResourceNo))
}
if req.Active != nil {
do = do.Where(q.Resource.Active.Is(*req.Active))
}
if req.Mode != nil {
do = do.Where(q.ResourceShort.As("Short").Type.Eq(int(*req.Mode)))
}
if req.CreatedAtStart != nil {
t := u.DateHead(*req.CreatedAtStart)
do = do.Where(q.Resource.CreatedAt.Gte(t))
}
if req.CreatedAtEnd != nil {
t := u.DateTail(*req.CreatedAtEnd)
do = do.Where(q.Resource.CreatedAt.Lte(t))
}
list, total, err := q.Resource.
Joins(q.Resource.User, q.Resource.Short, q.Resource.Short.Sku).
Select(
q.Resource.ALL,
q.User.As("User").Phone.As("User__phone"),
q.User.As("User").Name.As("User__name"),
q.ResourceShort.As("Short").Type.As("Short__type"),
q.ResourceShort.As("Short").Live.As("Short__live"),
q.ResourceShort.As("Short").Quota.As("Short__quota"),
q.ResourceShort.As("Short").Used.As("Short__used"),
q.ResourceShort.As("Short").Daily.As("Short__daily"),
q.ResourceShort.As("Short").LastAt.As("Short__last_at"),
q.ResourceShort.As("Short").ExpireAt.As("Short__expire_at"),
q.ProductSku.As("Short__Sku").Name.As("Short__Sku__name"),
).
Where(q.Resource.Type.Eq(int(m.ResourceTypeShort)), do).
Order(q.Resource.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return err
}
return c.JSON(core.PageResp{
List: list,
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
})
}
type PageResourceShortOfUserByAdminReq struct {
core.PageReq
UserID int32 `json:"user_id" validate:"required"`
ResourceNo *string `json:"resource_no"`
Active *bool `json:"active"`
Mode *int `json:"mode"`
CreatedAtStart *time.Time `json:"created_at_start"`
CreatedAtEnd *time.Time `json:"created_at_end"`
}
// PageResourceLongOfUserByAdmin 分页查询指定用户的长效套餐
func PageResourceLongOfUserByAdmin(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeResourceLongReadOfUser)
if err != nil {
return err
}
var req PageResourceLongOfUserByAdminReq
if err = g.Validator.ParseBody(c, &req); err != nil {
return err
}
do := q.Resource.Where(q.Resource.UserID.Eq(req.UserID))
if req.ResourceNo != nil {
do = do.Where(q.Resource.ResourceNo.Eq(*req.ResourceNo))
}
if req.Active != nil {
do = do.Where(q.Resource.Active.Is(*req.Active))
}
if req.Mode != nil {
do = do.Where(q.ResourceLong.As("Long").Type.Eq(*req.Mode))
}
if req.CreatedAtStart != nil {
t := u.DateHead(*req.CreatedAtStart)
do = do.Where(q.Resource.CreatedAt.Gte(t))
}
if req.CreatedAtEnd != nil {
t := u.DateTail(*req.CreatedAtEnd)
do = do.Where(q.Resource.CreatedAt.Lte(t))
}
list, total, err := q.Resource.
Joins(q.Resource.User, q.Resource.Long, q.Resource.Long.Sku).
Select(
q.Resource.ALL,
q.User.As("User").Phone.As("User__phone"),
q.User.As("User").Name.As("User__name"),
q.ResourceLong.As("Long").Type.As("Long__type"),
q.ResourceLong.As("Long").Live.As("Long__live"),
q.ResourceLong.As("Long").Quota.As("Long__quota"),
q.ResourceLong.As("Long").Used.As("Long__used"),
q.ResourceLong.As("Long").Daily.As("Long__daily"),
q.ResourceLong.As("Long").LastAt.As("Long__last_at"),
q.ResourceLong.As("Long").ExpireAt.As("Long__expire_at"),
q.ProductSku.As("Long__Sku").Name.As("Long__Sku__name"),
).
Where(q.Resource.Type.Eq(int(m.ResourceTypeLong)), do).
Order(q.Resource.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return err
}
return c.JSON(core.PageResp{
List: list,
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
})
}
type PageResourceLongOfUserByAdminReq struct {
core.PageReq
UserID int32 `json:"user_id" validate:"required"`
ResourceNo *string `json:"resource_no"`
Active *bool `json:"active"`
Mode *int `json:"mode"`
CreatedAtStart *time.Time `json:"created_at_start"`
CreatedAtEnd *time.Time `json:"created_at_end"`
}
// AllActiveResource 所有可用套餐 // AllActiveResource 所有可用套餐
func AllActiveResource(c *fiber.Ctx) error { func AllActiveResource(c *fiber.Ctx) error {
// 检查权限 // 检查权限
@@ -449,9 +591,6 @@ func AllActiveResource(c *fiber.Ctx) error {
return c.JSON(resources) return c.JSON(resources)
} }
type AllResourceReq struct {
}
func UpdateResourceByAdmin(c *fiber.Ctx) error { func UpdateResourceByAdmin(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeResourceWrite) _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeResourceWrite)
if err != nil { if err != nil {
@@ -658,10 +797,7 @@ type CreateResourceReq struct {
// ResourcePrice 套餐价格 // ResourcePrice 套餐价格
func ResourcePrice(c *fiber.Ctx) error { func ResourcePrice(c *fiber.Ctx) error {
// 检查权限 // 检查权限
_, err := auth.GetAuthCtx(c).PermitSecretClient() ac := auth.GetAuthCtx(c)
if err != nil {
return err
}
// 解析请求参数 // 解析请求参数
var req = new(CreateResourceReq) var req = new(CreateResourceReq)
@@ -670,16 +806,7 @@ func ResourcePrice(c *fiber.Ctx) error {
} }
// 获取套餐价格 // 获取套餐价格
// sku, err := s.Resource.GetSku(req.CreateResourceData.Code()) detail, err := req.TradeDetail(ac.User)
// if err != nil {
// return err
// }
// _, amount, discounted, couponApplied, err := s.Resource.GetPrice(sku, req.Count(), nil, nil)
// if err != nil {
// return err
// }
detail, err := req.TradeDetail(nil)
if err != nil { if err != nil {
return err return err
} }
@@ -687,11 +814,13 @@ func ResourcePrice(c *fiber.Ctx) error {
// 计算折扣 // 计算折扣
return c.JSON(ResourcePriceResp{ return c.JSON(ResourcePriceResp{
Price: detail.Amount.StringFixed(2), Price: detail.Amount.StringFixed(2),
Discounted: detail.Actual.StringFixed(2), Discounted: detail.Discounted.StringFixed(2),
Actual: detail.Actual.StringFixed(2),
}) })
} }
type ResourcePriceResp struct { type ResourcePriceResp struct {
Price string `json:"price"` Price string `json:"price"`
Discounted string `json:"discounted_price"` Discounted string `json:"discounted"`
Actual string `json:"actual"`
} }

View File

@@ -97,6 +97,84 @@ type PageTradeByAdminReq struct {
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"` CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
} }
// PageTradeOfUserByAdmin 分页查询指定用户的订单
func PageTradeOfUserByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeTradeReadOfUser)
if err != nil {
return err
}
// 解析请求参数
req := new(PageTradeOfUserByAdminReq)
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
// 构建查询语句
do := q.Trade.Where(q.Trade.UserID.Eq(req.UserID))
if req.InnerNo != nil {
do = do.Where(q.Trade.InnerNo.Eq(*req.InnerNo))
}
if req.OuterNo != nil {
do = do.Where(q.Trade.OuterNo.Eq(*req.OuterNo))
}
if req.Method != nil {
do = do.Where(q.Trade.Method.Eq(*req.Method))
}
if req.Platform != nil {
do = do.Where(q.Trade.Platform.Eq(*req.Platform))
}
if req.Status != nil {
do = do.Where(q.Trade.Status.Eq(*req.Status))
}
if req.CreatedAtStart != nil {
time := u.DateHead(*req.CreatedAtStart)
do = do.Where(q.Trade.CreatedAt.Gte(time))
}
if req.CreatedAtEnd != nil {
time := u.DateTail(*req.CreatedAtEnd)
do = do.Where(q.Trade.CreatedAt.Lte(time))
}
// 查询订单列表
list, total, err := q.Trade.
Joins(q.Trade.User).
Select(
q.Trade.ALL,
q.User.As("User").Phone.As("User__phone"),
q.User.As("User").Name.As("User__name"),
).
Where(do).
Order(q.Trade.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return err
}
// 返回结果
return c.JSON(core.PageResp{
List: list,
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
})
}
type PageTradeOfUserByAdminReq struct {
core.PageReq
UserID int32 `json:"user_id" validate:"required"`
InnerNo *string `json:"inner_no,omitempty"`
OuterNo *string `json:"outer_no,omitempty"`
Method *int `json:"method,omitempty"`
Platform *int `json:"platform,omitempty"`
Status *int `json:"status,omitempty"`
CreatedAtStart *time.Time `json:"created_at_start,omitempty"`
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
}
// ============================================================
// 创建订单 // 创建订单
func TradeCreate(c *fiber.Ctx) error { func TradeCreate(c *fiber.Ctx) error {
// 检查权限 // 检查权限
@@ -104,6 +182,9 @@ func TradeCreate(c *fiber.Ctx) error {
if err != nil { if err != nil {
return err return err
} }
if authCtx.User.IDType == m.UserIDTypeUnverified {
return core.NewBizErr("请先实名认证后再购买")
}
// 解析请求参数 // 解析请求参数
req := new(TradeCreateReq) req := new(TradeCreateReq)
@@ -143,6 +224,8 @@ type TradeCreateReq struct {
Recharge *s.UpdateBalanceData `json:"recharge,omitempty"` Recharge *s.UpdateBalanceData `json:"recharge,omitempty"`
} }
// ============================================================
// 完成订单 // 完成订单
func TradeComplete(c *fiber.Ctx) error { func TradeComplete(c *fiber.Ctx) error {
// 检查权限 // 检查权限
@@ -152,13 +235,13 @@ func TradeComplete(c *fiber.Ctx) error {
} }
// 解析请求参数 // 解析请求参数
req := new(TradeCompleteReq) var req s.TradeRef
if err := g.Validator.ParseBody(c, req); err != nil { if err := g.Validator.ParseBody(c, &req); err != nil {
return err return err
} }
// 检查订单状态 // 检查订单状态
err = s.Trade.CompleteTrade(authCtx.User, &req.TradeRef) err = s.Trade.CompleteTrade(authCtx.User, &req)
if err != nil { if err != nil {
return err return err
} }
@@ -166,10 +249,40 @@ func TradeComplete(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusNoContent) return c.SendStatus(fiber.StatusNoContent)
} }
type TradeCompleteReq struct { // 管理员完成订单
func TradeCompleteByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeTradeWriteComplete)
if err != nil {
return err
}
// 解析请求参数
var req struct {
s.TradeRef s.TradeRef
UserID int32 `json:"user_id" validate:"required"`
}
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
// 获取用户信息
user, err := s.User.Get(q.Q, req.UserID)
if err != nil {
return err
}
// 完成订单
err = s.Trade.CompleteTrade(user, &req.TradeRef)
if err != nil {
return err
}
return c.SendStatus(fiber.StatusNoContent)
} }
// ============================================================
// 取消订单 // 取消订单
func TradeCancel(c *fiber.Ctx) error { func TradeCancel(c *fiber.Ctx) error {
// 检查权限 // 检查权限
@@ -198,6 +311,8 @@ type TradeCancelReq struct {
s.TradeRef s.TradeRef
} }
// ============================================================
// 检查订单 // 检查订单
func TradeCheck(c *fiber.Ctx) error { func TradeCheck(c *fiber.Ctx) error {
// 检查权限sse 接口暂时不检查权限 // 检查权限sse 接口暂时不检查权限

View File

@@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"errors"
"platform/web/auth" "platform/web/auth"
"platform/web/core" "platform/web/core"
g "platform/web/globals" g "platform/web/globals"
@@ -11,6 +12,7 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"gorm.io/gen/field"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -67,7 +69,7 @@ func PageUserByAdmin(c *fiber.Ctx) error {
// 查询用户列表 // 查询用户列表
users, total, err := q.User. users, total, err := q.User.
Preload(q.User.Admin, q.User.Discount). Preload(q.User.Admin, q.User.Discount).
Omit(q.User.Password). Omit(q.User.Password, q.Admin.Password).
Where(do). Where(do).
Order(q.User.CreatedAt.Desc()). Order(q.User.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit()) FindByPage(req.GetOffset(), req.GetLimit())
@@ -76,6 +78,12 @@ func PageUserByAdmin(c *fiber.Ctx) error {
} }
for _, user := range users { for _, user := range users {
if user.IDNo != nil && len(*user.IDNo) == 18 {
var str = *user.IDNo
*user.IDNo = str[:6] + "****" + str[len(str)-2:]
}
if user.Admin != nil { if user.Admin != nil {
user.Admin = &m.Admin{ user.Admin = &m.Admin{
Name: user.Admin.Name, Name: user.Admin.Name,
@@ -133,7 +141,7 @@ func GetUserByAdmin(c *fiber.Ctx) error {
// 查询用户 // 查询用户
user, err := q.User. user, err := q.User.
Preload(q.User.Admin, q.User.Discount). Preload(q.User.Admin, q.User.Discount).
Omit(q.User.Password). Omit(q.User.Password, q.Admin.Password).
Where(do). Where(do).
Order(q.User.CreatedAt.Desc()). Order(q.User.CreatedAt.Desc()).
First() First()
@@ -298,14 +306,29 @@ func UpdateUser(c *fiber.Ctx) error {
} }
// 更新用户信息 // 更新用户信息
do := make([]field.AssignExpr, 0)
if req.Username != nil && *req.Username != "" {
do = append(do, q.User.Username.Value(*req.Username))
}
if req.Email != nil {
if *req.Email == "" {
do = append(do, q.User.Email.Null())
} else {
do = append(do, q.User.Email.Value(*req.Email))
}
}
if req.ContactQQ != nil {
do = append(do, q.User.ContactQQ.Value(*req.ContactQQ))
}
if req.ContactWechat != nil {
do = append(do, q.User.ContactWechat.Value(*req.ContactWechat))
}
_, err = q.User. _, err = q.User.
Where(q.User.ID.Eq(authCtx.User.ID)). Where(q.User.ID.Eq(authCtx.User.ID)).
Updates(m.User{ UpdateSimple(do...)
Username: &req.Username, if errors.Is(err, gorm.ErrDuplicatedKey) {
Email: &req.Email, return core.NewBizErr("用户名或邮箱已被占用")
ContactQQ: &req.ContactQQ, }
ContactWechat: &req.ContactWechat,
})
if err != nil { if err != nil {
return err return err
} }
@@ -315,10 +338,10 @@ func UpdateUser(c *fiber.Ctx) error {
} }
type UpdateUserReq struct { type UpdateUserReq struct {
Username string `json:"username" validate:"omitempty,min=3,max=20"` Username *string `json:"username" validate:"omitempty,min=3,max=20"`
Email string `json:"email" validate:"omitempty,email"` Email *string `json:"email" validate:"omitempty,email"`
ContactQQ string `json:"contact_qq" validate:"omitempty,qq"` ContactQQ *string `json:"contact_qq" validate:"omitempty,qq"`
ContactWechat string `json:"contact_wechat" validate:"omitempty,wechat"` ContactWechat *string `json:"contact_wechat" validate:"omitempty,wechat"`
} }
// 更新账号信息 // 更新账号信息
@@ -369,16 +392,14 @@ func UpdatePassword(c *fiber.Ctx) error {
return err return err
} }
// 验证手机号
if req.Phone != authCtx.User.Phone {
return fiber.NewError(fiber.StatusBadRequest, "手机号码不正确")
}
// 验证手机令牌 // 验证手机令牌
if req.Code == "" { if req.Code == "" {
return fiber.NewError(fiber.StatusBadRequest, "手机号码和验证码不能为空") return fiber.NewError(fiber.StatusBadRequest, "验证码不能为空")
}
err = s.Verifier.VerifySms(c.Context(), authCtx.User.Phone, req.Code, s.VerifierSmsPurposePassword)
if errors.Is(err, s.ErrVerifierServiceInvalid) {
return core.NewBizErr(s.ErrVerifierServiceInvalid.Error())
} }
err = s.Verifier.VerifySms(c.Context(), req.Phone, req.Code)
if err != nil { if err != nil {
return err return err
} }
@@ -401,7 +422,121 @@ func UpdatePassword(c *fiber.Ctx) error {
} }
type UpdatePasswordReq struct { type UpdatePasswordReq struct {
Phone string `json:"phone"`
Code string `json:"code"` Code string `json:"code"`
Password string `json:"password"` Password string `json:"password"`
} }
// PageUserNotBindByAdmin 分页获取未绑定管理员的用户
func PageUserNotBindByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserReadNotBind)
if err != nil {
return err
}
// 解析请求参数
req := new(PageUserNotBindByAdminReq)
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
// 构建查询条件(强制过滤未绑定管理员的用户)
do := q.User.Where(q.User.AdminID.IsNull())
if req.Phone != nil {
do = do.Where(q.User.Phone.Eq(*req.Phone))
}
// 查询用户列表
users, total, err := q.User.
Omit(q.User.Password, q.User.IDNo).
Where(do).
Order(q.User.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return err
}
// 返回结果
return c.JSON(core.PageResp{
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
List: users,
})
}
type PageUserNotBindByAdminReq struct {
core.PageReq
Phone *string `json:"phone,omitempty"`
}
// UpdateUserBalanceIncByAdmin 管理员增加用户余额
func UpdateUserBalanceIncByAdmin(c *fiber.Ctx) error {
authCtx, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWriteBalance)
if err != nil {
return err
}
var req UpdateUserBalanceChangeByAdminData
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
amount, err := decimal.NewFromString(req.Amount)
if err != nil {
return err
}
if !amount.IsPositive() {
return core.NewBizErr("金额必须为正数")
}
user, err := s.User.Get(q.Q, req.UserID)
if err != nil {
return err
}
newBalance := user.Balance.Add(amount)
if err := s.User.UpdateBalanceByAdmin(user, newBalance, &authCtx.Admin.ID); err != nil {
return err
}
return c.JSON(nil)
}
// UpdateUserBalanceDecByAdmin 管理员减少用户余额
func UpdateUserBalanceDecByAdmin(c *fiber.Ctx) error {
authCtx, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWriteBalance)
if err != nil {
return err
}
var req UpdateUserBalanceChangeByAdminData
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
amount, err := decimal.NewFromString(req.Amount)
if err != nil {
return err
}
if !amount.IsPositive() {
return core.NewBizErr("金额必须为正数")
}
user, err := s.User.Get(q.Q, req.UserID)
if err != nil {
return err
}
newBalance := user.Balance.Sub(amount)
if err := s.User.UpdateBalanceByAdmin(user, newBalance, &authCtx.Admin.ID); err != nil {
return err
}
return c.JSON(nil)
}
type UpdateUserBalanceChangeByAdminData struct {
UserID int32 `json:"user_id" validate:"required"`
Amount string `json:"amount" validate:"required"`
}

View File

@@ -5,6 +5,7 @@ import (
"platform/pkg/env" "platform/pkg/env"
"platform/web/auth" "platform/web/auth"
"platform/web/services" "platform/web/services"
s "platform/web/services"
"regexp" "regexp"
"strconv" "strconv"
@@ -13,12 +14,11 @@ import (
) )
type VerifierReq struct { type VerifierReq struct {
Purpose services.VerifierSmsPurpose `json:"purpose"` Purpose s.VerifierSmsPurpose `json:"purpose"`
Phone string `json:"phone"` Phone string `json:"phone"`
} }
func SmsCode(c *fiber.Ctx) error { func SendSmsCode(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitOfficialClient() _, err := auth.GetAuthCtx(c).PermitOfficialClient()
if err != nil { if err != nil {
return err return err
@@ -38,9 +38,9 @@ func SmsCode(c *fiber.Ctx) error {
} }
// 发送身份验证码 // 发送身份验证码
err = services.Verifier.SendSms(c.Context(), req.Phone, req.Purpose) err = s.Verifier.SendSms(c.Context(), req.Phone, req.Purpose)
if err != nil { if err != nil {
var sErr services.VerifierServiceSendLimitErr var sErr s.VerifierServiceSendLimitErr
if errors.As(err, &sErr) { if errors.As(err, &sErr) {
return fiber.NewError(fiber.StatusTooManyRequests, strconv.Itoa(int(sErr))) return fiber.NewError(fiber.StatusTooManyRequests, strconv.Itoa(int(sErr)))
} }
@@ -51,6 +51,23 @@ func SmsCode(c *fiber.Ctx) error {
return nil return nil
} }
func SendSmsCodeForPassword(c *fiber.Ctx) error {
ac, err := auth.GetAuthCtx(c).PermitUser()
if err != nil {
return err
}
if err := s.Verifier.SendSms(c.Context(), ac.User.Phone, s.VerifierSmsPurposePassword); err != nil {
var sErr s.VerifierServiceSendLimitErr
if errors.As(err, &sErr) {
return fiber.NewError(fiber.StatusTooManyRequests, strconv.Itoa(int(sErr)))
}
return err
}
return nil
}
func DebugGetSmsCode(c *fiber.Ctx) error { func DebugGetSmsCode(c *fiber.Ctx) error {
if env.RunMode != env.RunModeDev { if env.RunMode != env.RunModeDev {
return fiber.NewError(fiber.StatusForbidden, "not allowed") return fiber.NewError(fiber.StatusForbidden, "not allowed")

View File

@@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"errors"
"platform/pkg/env" "platform/pkg/env"
"platform/pkg/u" "platform/pkg/u"
"platform/web/auth" "platform/web/auth"
@@ -92,7 +93,7 @@ func CreateWhitelist(c *fiber.Ctx) error {
ip, err := secureAddr(req.Host) ip, err := secureAddr(req.Host)
if err != nil { if err != nil {
return err return core.NewBizErr("IP 地址无效", err)
} }
// 创建白名单 // 创建白名单
@@ -132,7 +133,7 @@ func UpdateWhitelist(c *fiber.Ctx) error {
ip, err := secureAddr(req.Host) ip, err := secureAddr(req.Host)
if err != nil { if err != nil {
return err return core.NewBizErr("IP 地址无效", err)
} }
// 更新白名单 // 更新白名单
@@ -201,7 +202,7 @@ func secureAddr(str string) (*orm.Inet, error) {
return nil, err return nil, err
} }
if !ip.IsGlobalUnicast() && env.RunMode != env.RunModeDev { if !ip.IsGlobalUnicast() && env.RunMode != env.RunModeDev {
return nil, fiber.NewError(fiber.StatusBadRequest, "IP 地址不可用") return nil, errors.New("IP 地址不可用")
} }
return ip, nil return ip, nil
} }

View File

@@ -11,7 +11,7 @@ import (
type Admin struct { type Admin struct {
core.Model core.Model
Username string `json:"username" gorm:"column:username"` // 用户名 Username string `json:"username" gorm:"column:username"` // 用户名
Password string `json:"password" gorm:"column:password"` // 密码 Password string `json:"-" gorm:"column:password"` // 密码
Name *string `json:"name,omitempty" gorm:"column:name"` // 真实姓名 Name *string `json:"name,omitempty" gorm:"column:name"` // 真实姓名
Avatar *string `json:"avatar,omitempty" gorm:"column:avatar"` // 头像URL Avatar *string `json:"avatar,omitempty" gorm:"column:avatar"` // 头像URL
Phone *string `json:"phone,omitempty" gorm:"column:phone"` // 手机号码 Phone *string `json:"phone,omitempty" gorm:"column:phone"` // 手机号码
@@ -20,6 +20,7 @@ type Admin struct {
LastLogin *time.Time `json:"last_login,omitempty" gorm:"column:last_login"` // 最后登录时间 LastLogin *time.Time `json:"last_login,omitempty" gorm:"column:last_login"` // 最后登录时间
LastLoginIP *orm.Inet `json:"last_login_ip,omitempty" gorm:"column:last_login_ip"` // 最后登录地址 LastLoginIP *orm.Inet `json:"last_login_ip,omitempty" gorm:"column:last_login_ip"` // 最后登录地址
LastLoginUA *string `json:"last_login_ua,omitempty" gorm:"column:last_login_ua"` // 最后登录代理 LastLoginUA *string `json:"last_login_ua,omitempty" gorm:"column:last_login_ua"` // 最后登录代理
Lock bool `json:"lock" gorm:"column:lock"` // 是否锁定编辑
Roles []*AdminRole `json:"roles" gorm:"many2many:link_admin_role"` Roles []*AdminRole `json:"roles" gorm:"many2many:link_admin_role"`
} }

View File

@@ -18,5 +18,5 @@ type BalanceActivity struct {
User *User `json:"user,omitempty" gorm:"foreignKey:UserID"` User *User `json:"user,omitempty" gorm:"foreignKey:UserID"`
Bill *Bill `json:"bill,omitempty" gorm:"foreignKey:BillID"` Bill *Bill `json:"bill,omitempty" gorm:"foreignKey:BillID"`
Admin *User `json:"admin,omitempty" gorm:"foreignKey:AdminID"` Admin *Admin `json:"admin,omitempty" gorm:"foreignKey:AdminID"`
} }

View File

@@ -13,7 +13,7 @@ type Bill struct {
TradeID *int32 `json:"trade_id,omitempty" gorm:"column:trade_id"` // 订单ID TradeID *int32 `json:"trade_id,omitempty" gorm:"column:trade_id"` // 订单ID
ResourceID *int32 `json:"resource_id,omitempty" gorm:"column:resource_id"` // 套餐ID ResourceID *int32 `json:"resource_id,omitempty" gorm:"column:resource_id"` // 套餐ID
RefundID *int32 `json:"refund_id,omitempty" gorm:"column:refund_id"` // 退款ID RefundID *int32 `json:"refund_id,omitempty" gorm:"column:refund_id"` // 退款ID
CouponID *int32 `json:"coupon_id,omitempty" gorm:"column:coupon_id"` // 优惠券ID CouponUserID *int32 `json:"coupon_user_id,omitempty" gorm:"column:coupon_user_id"` // 优惠券发放ID
BillNo string `json:"bill_no" gorm:"column:bill_no"` // 易读账单号 BillNo string `json:"bill_no" gorm:"column:bill_no"` // 易读账单号
Info *string `json:"info,omitempty" gorm:"column:info"` // 产品可读信息 Info *string `json:"info,omitempty" gorm:"column:info"` // 产品可读信息
Type BillType `json:"type" gorm:"column:type"` // 账单类型1-消费2-退款3-充值 Type BillType `json:"type" gorm:"column:type"` // 账单类型1-消费2-退款3-充值
@@ -24,6 +24,7 @@ type Bill struct {
Trade *Trade `json:"trade,omitempty" gorm:"foreignKey:TradeID"` Trade *Trade `json:"trade,omitempty" gorm:"foreignKey:TradeID"`
Resource *Resource `json:"resource,omitempty" gorm:"foreignKey:ResourceID"` Resource *Resource `json:"resource,omitempty" gorm:"foreignKey:ResourceID"`
Refund *Refund `json:"refund,omitempty" gorm:"foreignKey:RefundID"` Refund *Refund `json:"refund,omitempty" gorm:"foreignKey:RefundID"`
CouponUser *CouponUser `json:"coupon,omitempty" gorm:"foreignKey:CouponUserID"`
} }
// BillType 账单类型枚举 // BillType 账单类型枚举

View File

@@ -8,7 +8,7 @@ import (
type Client struct { type Client struct {
core.Model core.Model
ClientID string `json:"client_id" gorm:"column:client_id"` // OAuth2客户端标识符 ClientID string `json:"client_id" gorm:"column:client_id"` // OAuth2客户端标识符
ClientSecret string `json:"client_secret" gorm:"column:client_secret"` // OAuth2客户端密钥 ClientSecret string `json:"-" gorm:"column:client_secret"` // OAuth2客户端密钥
RedirectURI *string `json:"redirect_uri,omitempty" gorm:"column:redirect_uri"` // OAuth2 重定向URI RedirectURI *string `json:"redirect_uri,omitempty" gorm:"column:redirect_uri"` // OAuth2 重定向URI
Spec ClientSpec `json:"spec" gorm:"column:spec"` // 安全规范1-native2-browser3-web4-api Spec ClientSpec `json:"spec" gorm:"column:spec"` // 安全规范1-native2-browser3-web4-api
Name string `json:"name" gorm:"column:name"` // 名称 Name string `json:"name" gorm:"column:name"` // 名称

View File

@@ -10,20 +10,29 @@ import (
// Coupon 优惠券表 // Coupon 优惠券表
type Coupon struct { type Coupon struct {
core.Model core.Model
UserID *int32 `json:"user_id,omitempty" gorm:"column:user_id"` // 用户ID Name string `json:"name" gorm:"column:name"` // 优惠券名称
Code string `json:"code" gorm:"column:code"` // 优惠券代码
Remark *string `json:"remark,omitempty" gorm:"column:remark"` // 优惠券备注
Amount decimal.Decimal `json:"amount" gorm:"column:amount"` // 优惠券金额 Amount decimal.Decimal `json:"amount" gorm:"column:amount"` // 优惠券金额
MinAmount decimal.Decimal `json:"min_amount" gorm:"column:min_amount"` // 最低消费金额 MinAmount decimal.Decimal `json:"min_amount" gorm:"column:min_amount"` // 最低消费金额
Status CouponStatus `json:"status" gorm:"column:status"` // 优惠券状态0-未使用1-已使用2-已过期 Count int32 `json:"count" gorm:"column:count"` // 优惠券数量
ExpireAt *time.Time `json:"expire_at,omitempty" gorm:"column:expire_at"` // 过期时间 Status CouponStatus `json:"status" gorm:"column:status"` // 优惠券状态0-禁用1-正常
ExpireType CouponExpireType `json:"expire_type" gorm:"column:expire_type"` // 过期类型0-不过期1-固定日期2-相对日期(从发放时间算起)
ExpireAt *time.Time `json:"expire_at,omitempty" gorm:"column:expire_at"` // 过期时间,固定日期必填
ExpireIn *int `json:"expire_in,omitempty" gorm:"column:expire_in"` // 过期时长(天),相对日期必填
} }
// CouponStatus 优惠券状态枚举 // CouponStatus 优惠券使用状态枚举
type CouponStatus int type CouponStatus int
const ( const (
CouponStatusUnused CouponStatus = 0 // 未使 CouponStatusDisabled CouponStatus = 0 //
CouponStatusUsed CouponStatus = 1 // 已使用 CouponStatusEnabled CouponStatus = 1 // 正常
CouponStatusExpired CouponStatus = 2 // 已过期 )
// CouponExpireType 优惠券过期类型枚举
type CouponExpireType int
const (
CouponExpireTypeNever CouponExpireType = 0 // 不过期
CouponExpireTypeFixed CouponExpireType = 1 // 固定日期
CouponExpireTypeRelative CouponExpireType = 2 // 相对日期
) )

25
web/models/coupon_user.go Normal file
View File

@@ -0,0 +1,25 @@
package models
import "time"
// CouponUser 优惠券发放表
type CouponUser struct {
ID int32 `json:"id" gorm:"column:id;primaryKey"` // 记录ID
CouponID int32 `json:"coupon_id" gorm:"column:coupon_id"` // 优惠券ID
UserID int32 `json:"user_id" gorm:"column:user_id"` // 用户ID
Status CouponStatus `json:"status" gorm:"column:status"` // 使用状态0-未使用1-已使用
ExpireAt *time.Time `json:"expire_at,omitempty" gorm:"column:expire_at"` // 过期时间
UsedAt *time.Time `json:"used_at,omitempty" gorm:"column:used_at"` // 使用时间
CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` // 创建时间
Coupon *Coupon `json:"coupon,omitempty" gorm:"foreignKey:CouponID"`
User *User `json:"user,omitempty" gorm:"foreignKey:UserID"`
}
// CouponUserStatus 优惠券发放状态枚举
type CouponUserStatus int
const (
CouponUserStatusUnused CouponUserStatus = 0 // 未使用
CouponUserStatusUsed CouponUserStatus = 1 // 已使用
)

View File

@@ -12,6 +12,8 @@ type Product struct {
Description *string `json:"description,omitempty" gorm:"column:description"` // 产品描述 Description *string `json:"description,omitempty" gorm:"column:description"` // 产品描述
Sort int32 `json:"sort" gorm:"column:sort"` // 排序 Sort int32 `json:"sort" gorm:"column:sort"` // 排序
Status ProductStatus `json:"status" gorm:"column:status"` // 产品状态0-禁用1-正常 Status ProductStatus `json:"status" gorm:"column:status"` // 产品状态0-禁用1-正常
Skus []*ProductSku `json:"skus,omitempty" gorm:"foreignKey:ProductID"` // 产品包含的SKU列表
} }
// ProductStatus 产品状态枚举 // ProductStatus 产品状态枚举

View File

@@ -14,7 +14,19 @@ type ProductSku struct {
Code string `json:"code" gorm:"column:code"` // SSKU 代码:格式为 key=value,key=value,...其中key:value 是 SKU 的属性,多个属性用逗号分隔 Code string `json:"code" gorm:"column:code"` // SSKU 代码:格式为 key=value,key=value,...其中key:value 是 SKU 的属性,多个属性用逗号分隔
Name string `json:"name" gorm:"column:name"` // SKU 可读名称 Name string `json:"name" gorm:"column:name"` // SKU 可读名称
Price decimal.Decimal `json:"price" gorm:"column:price"` // 定价 Price decimal.Decimal `json:"price" gorm:"column:price"` // 定价
PriceMin decimal.Decimal `json:"price_min" gorm:"column:price_min"` // 最低价格
Status SkuStatus `json:"status" gorm:"column:status"` // SKU 状态0-禁用1-正常
Sort int32 `json:"sort" gorm:"column:sort"` // 排序
CountMin int32 `json:"count_min" gorm:"column:count_min"` // 最小购买数量
Product *Product `json:"product,omitempty" gorm:"foreignKey:ProductID"` Product *Product `json:"product,omitempty" gorm:"foreignKey:ProductID"`
Discount *ProductDiscount `json:"discount,omitempty" gorm:"foreignKey:DiscountId"` Discount *ProductDiscount `json:"discount,omitempty" gorm:"foreignKey:DiscountId"`
} }
// SkuStatus SKU 状态
type SkuStatus int32
const (
SkuStatusDisabled SkuStatus = 0 // 禁用
SkuStatusEnabled SkuStatus = 1 // 正常
)

View File

@@ -16,7 +16,7 @@ type User struct {
Phone string `json:"phone" gorm:"column:phone"` // 手机号码 Phone string `json:"phone" gorm:"column:phone"` // 手机号码
Username *string `json:"username,omitempty" gorm:"column:username"` // 用户名 Username *string `json:"username,omitempty" gorm:"column:username"` // 用户名
Email *string `json:"email,omitempty" gorm:"column:email"` // 邮箱 Email *string `json:"email,omitempty" gorm:"column:email"` // 邮箱
Password *string `json:"password,omitempty" gorm:"column:password"` // 用户密码 Password *string `json:"-" gorm:"column:password"` // 用户密码
Source *UserSource `json:"source,omitempty" gorm:"column:source"` // 用户来源0-官网注册1-管理员添加2-代理商注册3-代理商添加 Source *UserSource `json:"source,omitempty" gorm:"column:source"` // 用户来源0-官网注册1-管理员添加2-代理商注册3-代理商添加
Name *string `json:"name,omitempty" gorm:"column:name"` // 真实姓名 Name *string `json:"name,omitempty" gorm:"column:name"` // 真实姓名
Avatar *string `json:"avatar,omitempty" gorm:"column:avatar"` // 头像URL Avatar *string `json:"avatar,omitempty" gorm:"column:avatar"` // 头像URL

View File

@@ -41,6 +41,7 @@ func newAdmin(db *gorm.DB, opts ...gen.DOOption) admin {
_admin.LastLogin = field.NewTime(tableName, "last_login") _admin.LastLogin = field.NewTime(tableName, "last_login")
_admin.LastLoginIP = field.NewField(tableName, "last_login_ip") _admin.LastLoginIP = field.NewField(tableName, "last_login_ip")
_admin.LastLoginUA = field.NewString(tableName, "last_login_ua") _admin.LastLoginUA = field.NewString(tableName, "last_login_ua")
_admin.Lock = field.NewBool(tableName, "lock")
_admin.Roles = adminManyToManyRoles{ _admin.Roles = adminManyToManyRoles{
db: db.Session(&gorm.Session{}), db: db.Session(&gorm.Session{}),
@@ -91,6 +92,7 @@ type admin struct {
LastLogin field.Time LastLogin field.Time
LastLoginIP field.Field LastLoginIP field.Field
LastLoginUA field.String LastLoginUA field.String
Lock field.Bool
Roles adminManyToManyRoles Roles adminManyToManyRoles
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
@@ -122,6 +124,7 @@ func (a *admin) updateTableName(table string) *admin {
a.LastLogin = field.NewTime(table, "last_login") a.LastLogin = field.NewTime(table, "last_login")
a.LastLoginIP = field.NewField(table, "last_login_ip") a.LastLoginIP = field.NewField(table, "last_login_ip")
a.LastLoginUA = field.NewString(table, "last_login_ua") a.LastLoginUA = field.NewString(table, "last_login_ua")
a.Lock = field.NewBool(table, "lock")
a.fillFieldMap() a.fillFieldMap()
@@ -138,7 +141,7 @@ func (a *admin) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (a *admin) fillFieldMap() { func (a *admin) fillFieldMap() {
a.fieldMap = make(map[string]field.Expr, 15) a.fieldMap = make(map[string]field.Expr, 16)
a.fieldMap["id"] = a.ID a.fieldMap["id"] = a.ID
a.fieldMap["created_at"] = a.CreatedAt a.fieldMap["created_at"] = a.CreatedAt
a.fieldMap["updated_at"] = a.UpdatedAt a.fieldMap["updated_at"] = a.UpdatedAt
@@ -153,6 +156,7 @@ func (a *admin) fillFieldMap() {
a.fieldMap["last_login"] = a.LastLogin a.fieldMap["last_login"] = a.LastLogin
a.fieldMap["last_login_ip"] = a.LastLoginIP a.fieldMap["last_login_ip"] = a.LastLoginIP
a.fieldMap["last_login_ua"] = a.LastLoginUA a.fieldMap["last_login_ua"] = a.LastLoginUA
a.fieldMap["lock"] = a.Lock
} }

View File

@@ -36,10 +36,10 @@ func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity {
_balanceActivity.BalanceCurr = field.NewString(tableName, "balance_curr") _balanceActivity.BalanceCurr = field.NewString(tableName, "balance_curr")
_balanceActivity.Remark = field.NewString(tableName, "remark") _balanceActivity.Remark = field.NewString(tableName, "remark")
_balanceActivity.CreatedAt = field.NewTime(tableName, "created_at") _balanceActivity.CreatedAt = field.NewTime(tableName, "created_at")
_balanceActivity.Admin = balanceActivityHasOneAdmin{ _balanceActivity.User = balanceActivityBelongsToUser{
db: db.Session(&gorm.Session{}), db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Admin", "models.User"), RelationField: field.NewRelation("User", "models.User"),
Admin: struct { Admin: struct {
field.RelationField field.RelationField
Roles struct { Roles struct {
@@ -55,7 +55,7 @@ func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity {
} }
} }
}{ }{
RelationField: field.NewRelation("Admin.Admin", "models.Admin"), RelationField: field.NewRelation("User.Admin", "models.Admin"),
Roles: struct { Roles: struct {
field.RelationField field.RelationField
Permissions struct { Permissions struct {
@@ -68,7 +68,7 @@ func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity {
} }
} }
}{ }{
RelationField: field.NewRelation("Admin.Admin.Roles", "models.AdminRole"), RelationField: field.NewRelation("User.Admin.Roles", "models.AdminRole"),
Permissions: struct { Permissions: struct {
field.RelationField field.RelationField
Parent struct { Parent struct {
@@ -78,16 +78,16 @@ func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity {
field.RelationField field.RelationField
} }
}{ }{
RelationField: field.NewRelation("Admin.Admin.Roles.Permissions", "models.Permission"), RelationField: field.NewRelation("User.Admin.Roles.Permissions", "models.Permission"),
Parent: struct { Parent: struct {
field.RelationField field.RelationField
}{ }{
RelationField: field.NewRelation("Admin.Admin.Roles.Permissions.Parent", "models.Permission"), RelationField: field.NewRelation("User.Admin.Roles.Permissions.Parent", "models.Permission"),
}, },
Children: struct { Children: struct {
field.RelationField field.RelationField
}{ }{
RelationField: field.NewRelation("Admin.Admin.Roles.Permissions.Children", "models.Permission"), RelationField: field.NewRelation("User.Admin.Roles.Permissions.Children", "models.Permission"),
}, },
}, },
}, },
@@ -95,7 +95,7 @@ func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity {
Discount: struct { Discount: struct {
field.RelationField field.RelationField
}{ }{
RelationField: field.NewRelation("Admin.Discount", "models.ProductDiscount"), RelationField: field.NewRelation("User.Discount", "models.ProductDiscount"),
}, },
Roles: struct { Roles: struct {
field.RelationField field.RelationField
@@ -103,21 +103,15 @@ func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity {
field.RelationField field.RelationField
} }
}{ }{
RelationField: field.NewRelation("Admin.Roles", "models.UserRole"), RelationField: field.NewRelation("User.Roles", "models.UserRole"),
Permissions: struct { Permissions: struct {
field.RelationField field.RelationField
}{ }{
RelationField: field.NewRelation("Admin.Roles.Permissions", "models.Permission"), RelationField: field.NewRelation("User.Roles.Permissions", "models.Permission"),
}, },
}, },
} }
_balanceActivity.User = balanceActivityBelongsToUser{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("User", "models.User"),
}
_balanceActivity.Bill = balanceActivityBelongsToBill{ _balanceActivity.Bill = balanceActivityBelongsToBill{
db: db.Session(&gorm.Session{}), db: db.Session(&gorm.Session{}),
@@ -151,6 +145,9 @@ func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -179,6 +176,9 @@ func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -190,6 +190,9 @@ func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -198,8 +201,16 @@ func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity {
RelationField: field.NewRelation("Bill.Resource.Short.Sku", "models.ProductSku"), RelationField: field.NewRelation("Bill.Resource.Short.Sku", "models.ProductSku"),
Product: struct { Product: struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
}{ }{
RelationField: field.NewRelation("Bill.Resource.Short.Sku.Product", "models.Product"), RelationField: field.NewRelation("Bill.Resource.Short.Sku.Product", "models.Product"),
Skus: struct {
field.RelationField
}{
RelationField: field.NewRelation("Bill.Resource.Short.Sku.Product.Skus", "models.ProductSku"),
},
}, },
Discount: struct { Discount: struct {
field.RelationField field.RelationField
@@ -232,6 +243,33 @@ func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity {
}{ }{
RelationField: field.NewRelation("Bill.Refund", "models.Refund"), RelationField: field.NewRelation("Bill.Refund", "models.Refund"),
}, },
CouponUser: struct {
field.RelationField
Coupon struct {
field.RelationField
}
User struct {
field.RelationField
}
}{
RelationField: field.NewRelation("Bill.CouponUser", "models.CouponUser"),
Coupon: struct {
field.RelationField
}{
RelationField: field.NewRelation("Bill.CouponUser.Coupon", "models.Coupon"),
},
User: struct {
field.RelationField
}{
RelationField: field.NewRelation("Bill.CouponUser.User", "models.User"),
},
},
}
_balanceActivity.Admin = balanceActivityBelongsToAdmin{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Admin", "models.Admin"),
} }
_balanceActivity.fillFieldMap() _balanceActivity.fillFieldMap()
@@ -252,12 +290,12 @@ type balanceActivity struct {
BalanceCurr field.String BalanceCurr field.String
Remark field.String Remark field.String
CreatedAt field.Time CreatedAt field.Time
Admin balanceActivityHasOneAdmin
User balanceActivityBelongsToUser User balanceActivityBelongsToUser
Bill balanceActivityBelongsToBill Bill balanceActivityBelongsToBill
Admin balanceActivityBelongsToAdmin
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -313,24 +351,24 @@ func (b *balanceActivity) fillFieldMap() {
func (b balanceActivity) clone(db *gorm.DB) balanceActivity { func (b balanceActivity) clone(db *gorm.DB) balanceActivity {
b.balanceActivityDo.ReplaceConnPool(db.Statement.ConnPool) b.balanceActivityDo.ReplaceConnPool(db.Statement.ConnPool)
b.Admin.db = db.Session(&gorm.Session{Initialized: true})
b.Admin.db.Statement.ConnPool = db.Statement.ConnPool
b.User.db = db.Session(&gorm.Session{Initialized: true}) b.User.db = db.Session(&gorm.Session{Initialized: true})
b.User.db.Statement.ConnPool = db.Statement.ConnPool b.User.db.Statement.ConnPool = db.Statement.ConnPool
b.Bill.db = db.Session(&gorm.Session{Initialized: true}) b.Bill.db = db.Session(&gorm.Session{Initialized: true})
b.Bill.db.Statement.ConnPool = db.Statement.ConnPool b.Bill.db.Statement.ConnPool = db.Statement.ConnPool
b.Admin.db = db.Session(&gorm.Session{Initialized: true})
b.Admin.db.Statement.ConnPool = db.Statement.ConnPool
return b return b
} }
func (b balanceActivity) replaceDB(db *gorm.DB) balanceActivity { func (b balanceActivity) replaceDB(db *gorm.DB) balanceActivity {
b.balanceActivityDo.ReplaceDB(db) b.balanceActivityDo.ReplaceDB(db)
b.Admin.db = db.Session(&gorm.Session{})
b.User.db = db.Session(&gorm.Session{}) b.User.db = db.Session(&gorm.Session{})
b.Bill.db = db.Session(&gorm.Session{}) b.Bill.db = db.Session(&gorm.Session{})
b.Admin.db = db.Session(&gorm.Session{})
return b return b
} }
type balanceActivityHasOneAdmin struct { type balanceActivityBelongsToUser struct {
db *gorm.DB db *gorm.DB
field.RelationField field.RelationField
@@ -361,87 +399,6 @@ type balanceActivityHasOneAdmin struct {
} }
} }
func (a balanceActivityHasOneAdmin) Where(conds ...field.Expr) *balanceActivityHasOneAdmin {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a balanceActivityHasOneAdmin) WithContext(ctx context.Context) *balanceActivityHasOneAdmin {
a.db = a.db.WithContext(ctx)
return &a
}
func (a balanceActivityHasOneAdmin) Session(session *gorm.Session) *balanceActivityHasOneAdmin {
a.db = a.db.Session(session)
return &a
}
func (a balanceActivityHasOneAdmin) Model(m *models.BalanceActivity) *balanceActivityHasOneAdminTx {
return &balanceActivityHasOneAdminTx{a.db.Model(m).Association(a.Name())}
}
func (a balanceActivityHasOneAdmin) Unscoped() *balanceActivityHasOneAdmin {
a.db = a.db.Unscoped()
return &a
}
type balanceActivityHasOneAdminTx struct{ tx *gorm.Association }
func (a balanceActivityHasOneAdminTx) Find() (result *models.User, err error) {
return result, a.tx.Find(&result)
}
func (a balanceActivityHasOneAdminTx) Append(values ...*models.User) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a balanceActivityHasOneAdminTx) Replace(values ...*models.User) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a balanceActivityHasOneAdminTx) Delete(values ...*models.User) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a balanceActivityHasOneAdminTx) Clear() error {
return a.tx.Clear()
}
func (a balanceActivityHasOneAdminTx) Count() int64 {
return a.tx.Count()
}
func (a balanceActivityHasOneAdminTx) Unscoped() *balanceActivityHasOneAdminTx {
a.tx = a.tx.Unscoped()
return &a
}
type balanceActivityBelongsToUser struct {
db *gorm.DB
field.RelationField
}
func (a balanceActivityBelongsToUser) Where(conds ...field.Expr) *balanceActivityBelongsToUser { func (a balanceActivityBelongsToUser) Where(conds ...field.Expr) *balanceActivityBelongsToUser {
if len(conds) == 0 { if len(conds) == 0 {
return &a return &a
@@ -542,6 +499,9 @@ type balanceActivityBelongsToBill struct {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -561,6 +521,15 @@ type balanceActivityBelongsToBill struct {
Refund struct { Refund struct {
field.RelationField field.RelationField
} }
CouponUser struct {
field.RelationField
Coupon struct {
field.RelationField
}
User struct {
field.RelationField
}
}
} }
func (a balanceActivityBelongsToBill) Where(conds ...field.Expr) *balanceActivityBelongsToBill { func (a balanceActivityBelongsToBill) Where(conds ...field.Expr) *balanceActivityBelongsToBill {
@@ -638,6 +607,87 @@ func (a balanceActivityBelongsToBillTx) Unscoped() *balanceActivityBelongsToBill
return &a return &a
} }
type balanceActivityBelongsToAdmin struct {
db *gorm.DB
field.RelationField
}
func (a balanceActivityBelongsToAdmin) Where(conds ...field.Expr) *balanceActivityBelongsToAdmin {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a balanceActivityBelongsToAdmin) WithContext(ctx context.Context) *balanceActivityBelongsToAdmin {
a.db = a.db.WithContext(ctx)
return &a
}
func (a balanceActivityBelongsToAdmin) Session(session *gorm.Session) *balanceActivityBelongsToAdmin {
a.db = a.db.Session(session)
return &a
}
func (a balanceActivityBelongsToAdmin) Model(m *models.BalanceActivity) *balanceActivityBelongsToAdminTx {
return &balanceActivityBelongsToAdminTx{a.db.Model(m).Association(a.Name())}
}
func (a balanceActivityBelongsToAdmin) Unscoped() *balanceActivityBelongsToAdmin {
a.db = a.db.Unscoped()
return &a
}
type balanceActivityBelongsToAdminTx struct{ tx *gorm.Association }
func (a balanceActivityBelongsToAdminTx) Find() (result *models.Admin, err error) {
return result, a.tx.Find(&result)
}
func (a balanceActivityBelongsToAdminTx) Append(values ...*models.Admin) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a balanceActivityBelongsToAdminTx) Replace(values ...*models.Admin) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a balanceActivityBelongsToAdminTx) Delete(values ...*models.Admin) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a balanceActivityBelongsToAdminTx) Clear() error {
return a.tx.Clear()
}
func (a balanceActivityBelongsToAdminTx) Count() int64 {
return a.tx.Count()
}
func (a balanceActivityBelongsToAdminTx) Unscoped() *balanceActivityBelongsToAdminTx {
a.tx = a.tx.Unscoped()
return &a
}
type balanceActivityDo struct{ gen.DO } type balanceActivityDo struct{ gen.DO }
func (b balanceActivityDo) Debug() *balanceActivityDo { func (b balanceActivityDo) Debug() *balanceActivityDo {

View File

@@ -35,7 +35,7 @@ func newBill(db *gorm.DB, opts ...gen.DOOption) bill {
_bill.TradeID = field.NewInt32(tableName, "trade_id") _bill.TradeID = field.NewInt32(tableName, "trade_id")
_bill.ResourceID = field.NewInt32(tableName, "resource_id") _bill.ResourceID = field.NewInt32(tableName, "resource_id")
_bill.RefundID = field.NewInt32(tableName, "refund_id") _bill.RefundID = field.NewInt32(tableName, "refund_id")
_bill.CouponID = field.NewInt32(tableName, "coupon_id") _bill.CouponUserID = field.NewInt32(tableName, "coupon_user_id")
_bill.BillNo = field.NewString(tableName, "bill_no") _bill.BillNo = field.NewString(tableName, "bill_no")
_bill.Info = field.NewString(tableName, "info") _bill.Info = field.NewString(tableName, "info")
_bill.Type = field.NewInt(tableName, "type") _bill.Type = field.NewInt(tableName, "type")
@@ -143,6 +143,9 @@ func newBill(db *gorm.DB, opts ...gen.DOOption) bill {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -154,6 +157,9 @@ func newBill(db *gorm.DB, opts ...gen.DOOption) bill {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -162,8 +168,16 @@ func newBill(db *gorm.DB, opts ...gen.DOOption) bill {
RelationField: field.NewRelation("Resource.Short.Sku", "models.ProductSku"), RelationField: field.NewRelation("Resource.Short.Sku", "models.ProductSku"),
Product: struct { Product: struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
}{ }{
RelationField: field.NewRelation("Resource.Short.Sku.Product", "models.Product"), RelationField: field.NewRelation("Resource.Short.Sku.Product", "models.Product"),
Skus: struct {
field.RelationField
}{
RelationField: field.NewRelation("Resource.Short.Sku.Product.Skus", "models.ProductSku"),
},
}, },
Discount: struct { Discount: struct {
field.RelationField field.RelationField
@@ -198,6 +212,22 @@ func newBill(db *gorm.DB, opts ...gen.DOOption) bill {
RelationField: field.NewRelation("Refund", "models.Refund"), RelationField: field.NewRelation("Refund", "models.Refund"),
} }
_bill.CouponUser = billBelongsToCouponUser{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("CouponUser", "models.CouponUser"),
Coupon: struct {
field.RelationField
}{
RelationField: field.NewRelation("CouponUser.Coupon", "models.Coupon"),
},
User: struct {
field.RelationField
}{
RelationField: field.NewRelation("CouponUser.User", "models.User"),
},
}
_bill.fillFieldMap() _bill.fillFieldMap()
return _bill return _bill
@@ -215,7 +245,7 @@ type bill struct {
TradeID field.Int32 TradeID field.Int32
ResourceID field.Int32 ResourceID field.Int32
RefundID field.Int32 RefundID field.Int32
CouponID field.Int32 CouponUserID field.Int32
BillNo field.String BillNo field.String
Info field.String Info field.String
Type field.Int Type field.Int
@@ -229,6 +259,8 @@ type bill struct {
Refund billBelongsToRefund Refund billBelongsToRefund
CouponUser billBelongsToCouponUser
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -252,7 +284,7 @@ func (b *bill) updateTableName(table string) *bill {
b.TradeID = field.NewInt32(table, "trade_id") b.TradeID = field.NewInt32(table, "trade_id")
b.ResourceID = field.NewInt32(table, "resource_id") b.ResourceID = field.NewInt32(table, "resource_id")
b.RefundID = field.NewInt32(table, "refund_id") b.RefundID = field.NewInt32(table, "refund_id")
b.CouponID = field.NewInt32(table, "coupon_id") b.CouponUserID = field.NewInt32(table, "coupon_user_id")
b.BillNo = field.NewString(table, "bill_no") b.BillNo = field.NewString(table, "bill_no")
b.Info = field.NewString(table, "info") b.Info = field.NewString(table, "info")
b.Type = field.NewInt(table, "type") b.Type = field.NewInt(table, "type")
@@ -274,7 +306,7 @@ func (b *bill) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (b *bill) fillFieldMap() { func (b *bill) fillFieldMap() {
b.fieldMap = make(map[string]field.Expr, 18) b.fieldMap = make(map[string]field.Expr, 19)
b.fieldMap["id"] = b.ID b.fieldMap["id"] = b.ID
b.fieldMap["created_at"] = b.CreatedAt b.fieldMap["created_at"] = b.CreatedAt
b.fieldMap["updated_at"] = b.UpdatedAt b.fieldMap["updated_at"] = b.UpdatedAt
@@ -283,7 +315,7 @@ func (b *bill) fillFieldMap() {
b.fieldMap["trade_id"] = b.TradeID b.fieldMap["trade_id"] = b.TradeID
b.fieldMap["resource_id"] = b.ResourceID b.fieldMap["resource_id"] = b.ResourceID
b.fieldMap["refund_id"] = b.RefundID b.fieldMap["refund_id"] = b.RefundID
b.fieldMap["coupon_id"] = b.CouponID b.fieldMap["coupon_user_id"] = b.CouponUserID
b.fieldMap["bill_no"] = b.BillNo b.fieldMap["bill_no"] = b.BillNo
b.fieldMap["info"] = b.Info b.fieldMap["info"] = b.Info
b.fieldMap["type"] = b.Type b.fieldMap["type"] = b.Type
@@ -302,6 +334,8 @@ func (b bill) clone(db *gorm.DB) bill {
b.Resource.db.Statement.ConnPool = db.Statement.ConnPool b.Resource.db.Statement.ConnPool = db.Statement.ConnPool
b.Refund.db = db.Session(&gorm.Session{Initialized: true}) b.Refund.db = db.Session(&gorm.Session{Initialized: true})
b.Refund.db.Statement.ConnPool = db.Statement.ConnPool b.Refund.db.Statement.ConnPool = db.Statement.ConnPool
b.CouponUser.db = db.Session(&gorm.Session{Initialized: true})
b.CouponUser.db.Statement.ConnPool = db.Statement.ConnPool
return b return b
} }
@@ -311,6 +345,7 @@ func (b bill) replaceDB(db *gorm.DB) bill {
b.Trade.db = db.Session(&gorm.Session{}) b.Trade.db = db.Session(&gorm.Session{})
b.Resource.db = db.Session(&gorm.Session{}) b.Resource.db = db.Session(&gorm.Session{})
b.Refund.db = db.Session(&gorm.Session{}) b.Refund.db = db.Session(&gorm.Session{})
b.CouponUser.db = db.Session(&gorm.Session{})
return b return b
} }
@@ -519,6 +554,9 @@ type billBelongsToResource struct {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -692,6 +730,94 @@ func (a billBelongsToRefundTx) Unscoped() *billBelongsToRefundTx {
return &a return &a
} }
type billBelongsToCouponUser struct {
db *gorm.DB
field.RelationField
Coupon struct {
field.RelationField
}
User struct {
field.RelationField
}
}
func (a billBelongsToCouponUser) Where(conds ...field.Expr) *billBelongsToCouponUser {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a billBelongsToCouponUser) WithContext(ctx context.Context) *billBelongsToCouponUser {
a.db = a.db.WithContext(ctx)
return &a
}
func (a billBelongsToCouponUser) Session(session *gorm.Session) *billBelongsToCouponUser {
a.db = a.db.Session(session)
return &a
}
func (a billBelongsToCouponUser) Model(m *models.Bill) *billBelongsToCouponUserTx {
return &billBelongsToCouponUserTx{a.db.Model(m).Association(a.Name())}
}
func (a billBelongsToCouponUser) Unscoped() *billBelongsToCouponUser {
a.db = a.db.Unscoped()
return &a
}
type billBelongsToCouponUserTx struct{ tx *gorm.Association }
func (a billBelongsToCouponUserTx) Find() (result *models.CouponUser, err error) {
return result, a.tx.Find(&result)
}
func (a billBelongsToCouponUserTx) Append(values ...*models.CouponUser) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a billBelongsToCouponUserTx) Replace(values ...*models.CouponUser) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a billBelongsToCouponUserTx) Delete(values ...*models.CouponUser) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a billBelongsToCouponUserTx) Clear() error {
return a.tx.Clear()
}
func (a billBelongsToCouponUserTx) Count() int64 {
return a.tx.Count()
}
func (a billBelongsToCouponUserTx) Unscoped() *billBelongsToCouponUserTx {
a.tx = a.tx.Unscoped()
return &a
}
type billDo struct{ gen.DO } type billDo struct{ gen.DO }
func (b billDo) Debug() *billDo { func (b billDo) Debug() *billDo {

View File

@@ -138,6 +138,9 @@ func newChannel(db *gorm.DB, opts ...gen.DOOption) channel {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -149,6 +152,9 @@ func newChannel(db *gorm.DB, opts ...gen.DOOption) channel {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -157,8 +163,16 @@ func newChannel(db *gorm.DB, opts ...gen.DOOption) channel {
RelationField: field.NewRelation("Resource.Short.Sku", "models.ProductSku"), RelationField: field.NewRelation("Resource.Short.Sku", "models.ProductSku"),
Product: struct { Product: struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
}{ }{
RelationField: field.NewRelation("Resource.Short.Sku.Product", "models.Product"), RelationField: field.NewRelation("Resource.Short.Sku.Product", "models.Product"),
Skus: struct {
field.RelationField
}{
RelationField: field.NewRelation("Resource.Short.Sku.Product.Skus", "models.ProductSku"),
},
}, },
Discount: struct { Discount: struct {
field.RelationField field.RelationField
@@ -490,6 +504,9 @@ type channelBelongsToResource struct {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField

View File

@@ -31,13 +31,14 @@ func newCoupon(db *gorm.DB, opts ...gen.DOOption) coupon {
_coupon.CreatedAt = field.NewTime(tableName, "created_at") _coupon.CreatedAt = field.NewTime(tableName, "created_at")
_coupon.UpdatedAt = field.NewTime(tableName, "updated_at") _coupon.UpdatedAt = field.NewTime(tableName, "updated_at")
_coupon.DeletedAt = field.NewField(tableName, "deleted_at") _coupon.DeletedAt = field.NewField(tableName, "deleted_at")
_coupon.UserID = field.NewInt32(tableName, "user_id") _coupon.Name = field.NewString(tableName, "name")
_coupon.Code = field.NewString(tableName, "code")
_coupon.Remark = field.NewString(tableName, "remark")
_coupon.Amount = field.NewField(tableName, "amount") _coupon.Amount = field.NewField(tableName, "amount")
_coupon.MinAmount = field.NewField(tableName, "min_amount") _coupon.MinAmount = field.NewField(tableName, "min_amount")
_coupon.Count_ = field.NewInt32(tableName, "count")
_coupon.Status = field.NewInt(tableName, "status") _coupon.Status = field.NewInt(tableName, "status")
_coupon.ExpireType = field.NewInt(tableName, "expire_type")
_coupon.ExpireAt = field.NewTime(tableName, "expire_at") _coupon.ExpireAt = field.NewTime(tableName, "expire_at")
_coupon.ExpireIn = field.NewInt(tableName, "expire_in")
_coupon.fillFieldMap() _coupon.fillFieldMap()
@@ -52,13 +53,14 @@ type coupon struct {
CreatedAt field.Time CreatedAt field.Time
UpdatedAt field.Time UpdatedAt field.Time
DeletedAt field.Field DeletedAt field.Field
UserID field.Int32 Name field.String
Code field.String
Remark field.String
Amount field.Field Amount field.Field
MinAmount field.Field MinAmount field.Field
Count_ field.Int32
Status field.Int Status field.Int
ExpireType field.Int
ExpireAt field.Time ExpireAt field.Time
ExpireIn field.Int
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -79,13 +81,14 @@ func (c *coupon) updateTableName(table string) *coupon {
c.CreatedAt = field.NewTime(table, "created_at") c.CreatedAt = field.NewTime(table, "created_at")
c.UpdatedAt = field.NewTime(table, "updated_at") c.UpdatedAt = field.NewTime(table, "updated_at")
c.DeletedAt = field.NewField(table, "deleted_at") c.DeletedAt = field.NewField(table, "deleted_at")
c.UserID = field.NewInt32(table, "user_id") c.Name = field.NewString(table, "name")
c.Code = field.NewString(table, "code")
c.Remark = field.NewString(table, "remark")
c.Amount = field.NewField(table, "amount") c.Amount = field.NewField(table, "amount")
c.MinAmount = field.NewField(table, "min_amount") c.MinAmount = field.NewField(table, "min_amount")
c.Count_ = field.NewInt32(table, "count")
c.Status = field.NewInt(table, "status") c.Status = field.NewInt(table, "status")
c.ExpireType = field.NewInt(table, "expire_type")
c.ExpireAt = field.NewTime(table, "expire_at") c.ExpireAt = field.NewTime(table, "expire_at")
c.ExpireIn = field.NewInt(table, "expire_in")
c.fillFieldMap() c.fillFieldMap()
@@ -102,18 +105,19 @@ func (c *coupon) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (c *coupon) fillFieldMap() { func (c *coupon) fillFieldMap() {
c.fieldMap = make(map[string]field.Expr, 11) c.fieldMap = make(map[string]field.Expr, 12)
c.fieldMap["id"] = c.ID c.fieldMap["id"] = c.ID
c.fieldMap["created_at"] = c.CreatedAt c.fieldMap["created_at"] = c.CreatedAt
c.fieldMap["updated_at"] = c.UpdatedAt c.fieldMap["updated_at"] = c.UpdatedAt
c.fieldMap["deleted_at"] = c.DeletedAt c.fieldMap["deleted_at"] = c.DeletedAt
c.fieldMap["user_id"] = c.UserID c.fieldMap["name"] = c.Name
c.fieldMap["code"] = c.Code
c.fieldMap["remark"] = c.Remark
c.fieldMap["amount"] = c.Amount c.fieldMap["amount"] = c.Amount
c.fieldMap["min_amount"] = c.MinAmount c.fieldMap["min_amount"] = c.MinAmount
c.fieldMap["count"] = c.Count_
c.fieldMap["status"] = c.Status c.fieldMap["status"] = c.Status
c.fieldMap["expire_type"] = c.ExpireType
c.fieldMap["expire_at"] = c.ExpireAt c.fieldMap["expire_at"] = c.ExpireAt
c.fieldMap["expire_in"] = c.ExpireIn
} }
func (c coupon) clone(db *gorm.DB) coupon { func (c coupon) clone(db *gorm.DB) coupon {

View File

@@ -0,0 +1,621 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package queries
import (
"context"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/plugin/dbresolver"
"platform/web/models"
)
func newCouponUser(db *gorm.DB, opts ...gen.DOOption) couponUser {
_couponUser := couponUser{}
_couponUser.couponUserDo.UseDB(db, opts...)
_couponUser.couponUserDo.UseModel(&models.CouponUser{})
tableName := _couponUser.couponUserDo.TableName()
_couponUser.ALL = field.NewAsterisk(tableName)
_couponUser.ID = field.NewInt32(tableName, "id")
_couponUser.CouponID = field.NewInt32(tableName, "coupon_id")
_couponUser.UserID = field.NewInt32(tableName, "user_id")
_couponUser.Status = field.NewInt(tableName, "status")
_couponUser.ExpireAt = field.NewTime(tableName, "expire_at")
_couponUser.UsedAt = field.NewTime(tableName, "used_at")
_couponUser.CreatedAt = field.NewTime(tableName, "created_at")
_couponUser.Coupon = couponUserBelongsToCoupon{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Coupon", "models.Coupon"),
}
_couponUser.User = couponUserBelongsToUser{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("User", "models.User"),
Admin: struct {
field.RelationField
Roles struct {
field.RelationField
Permissions struct {
field.RelationField
Parent struct {
field.RelationField
}
Children struct {
field.RelationField
}
}
}
}{
RelationField: field.NewRelation("User.Admin", "models.Admin"),
Roles: struct {
field.RelationField
Permissions struct {
field.RelationField
Parent struct {
field.RelationField
}
Children struct {
field.RelationField
}
}
}{
RelationField: field.NewRelation("User.Admin.Roles", "models.AdminRole"),
Permissions: struct {
field.RelationField
Parent struct {
field.RelationField
}
Children struct {
field.RelationField
}
}{
RelationField: field.NewRelation("User.Admin.Roles.Permissions", "models.Permission"),
Parent: struct {
field.RelationField
}{
RelationField: field.NewRelation("User.Admin.Roles.Permissions.Parent", "models.Permission"),
},
Children: struct {
field.RelationField
}{
RelationField: field.NewRelation("User.Admin.Roles.Permissions.Children", "models.Permission"),
},
},
},
},
Discount: struct {
field.RelationField
}{
RelationField: field.NewRelation("User.Discount", "models.ProductDiscount"),
},
Roles: struct {
field.RelationField
Permissions struct {
field.RelationField
}
}{
RelationField: field.NewRelation("User.Roles", "models.UserRole"),
Permissions: struct {
field.RelationField
}{
RelationField: field.NewRelation("User.Roles.Permissions", "models.Permission"),
},
},
}
_couponUser.fillFieldMap()
return _couponUser
}
type couponUser struct {
couponUserDo
ALL field.Asterisk
ID field.Int32
CouponID field.Int32
UserID field.Int32
Status field.Int
ExpireAt field.Time
UsedAt field.Time
CreatedAt field.Time
Coupon couponUserBelongsToCoupon
User couponUserBelongsToUser
fieldMap map[string]field.Expr
}
func (c couponUser) Table(newTableName string) *couponUser {
c.couponUserDo.UseTable(newTableName)
return c.updateTableName(newTableName)
}
func (c couponUser) As(alias string) *couponUser {
c.couponUserDo.DO = *(c.couponUserDo.As(alias).(*gen.DO))
return c.updateTableName(alias)
}
func (c *couponUser) updateTableName(table string) *couponUser {
c.ALL = field.NewAsterisk(table)
c.ID = field.NewInt32(table, "id")
c.CouponID = field.NewInt32(table, "coupon_id")
c.UserID = field.NewInt32(table, "user_id")
c.Status = field.NewInt(table, "status")
c.ExpireAt = field.NewTime(table, "expire_at")
c.UsedAt = field.NewTime(table, "used_at")
c.CreatedAt = field.NewTime(table, "created_at")
c.fillFieldMap()
return c
}
func (c *couponUser) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
_f, ok := c.fieldMap[fieldName]
if !ok || _f == nil {
return nil, false
}
_oe, ok := _f.(field.OrderExpr)
return _oe, ok
}
func (c *couponUser) fillFieldMap() {
c.fieldMap = make(map[string]field.Expr, 9)
c.fieldMap["id"] = c.ID
c.fieldMap["coupon_id"] = c.CouponID
c.fieldMap["user_id"] = c.UserID
c.fieldMap["status"] = c.Status
c.fieldMap["expire_at"] = c.ExpireAt
c.fieldMap["used_at"] = c.UsedAt
c.fieldMap["created_at"] = c.CreatedAt
}
func (c couponUser) clone(db *gorm.DB) couponUser {
c.couponUserDo.ReplaceConnPool(db.Statement.ConnPool)
c.Coupon.db = db.Session(&gorm.Session{Initialized: true})
c.Coupon.db.Statement.ConnPool = db.Statement.ConnPool
c.User.db = db.Session(&gorm.Session{Initialized: true})
c.User.db.Statement.ConnPool = db.Statement.ConnPool
return c
}
func (c couponUser) replaceDB(db *gorm.DB) couponUser {
c.couponUserDo.ReplaceDB(db)
c.Coupon.db = db.Session(&gorm.Session{})
c.User.db = db.Session(&gorm.Session{})
return c
}
type couponUserBelongsToCoupon struct {
db *gorm.DB
field.RelationField
}
func (a couponUserBelongsToCoupon) Where(conds ...field.Expr) *couponUserBelongsToCoupon {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a couponUserBelongsToCoupon) WithContext(ctx context.Context) *couponUserBelongsToCoupon {
a.db = a.db.WithContext(ctx)
return &a
}
func (a couponUserBelongsToCoupon) Session(session *gorm.Session) *couponUserBelongsToCoupon {
a.db = a.db.Session(session)
return &a
}
func (a couponUserBelongsToCoupon) Model(m *models.CouponUser) *couponUserBelongsToCouponTx {
return &couponUserBelongsToCouponTx{a.db.Model(m).Association(a.Name())}
}
func (a couponUserBelongsToCoupon) Unscoped() *couponUserBelongsToCoupon {
a.db = a.db.Unscoped()
return &a
}
type couponUserBelongsToCouponTx struct{ tx *gorm.Association }
func (a couponUserBelongsToCouponTx) Find() (result *models.Coupon, err error) {
return result, a.tx.Find(&result)
}
func (a couponUserBelongsToCouponTx) Append(values ...*models.Coupon) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a couponUserBelongsToCouponTx) Replace(values ...*models.Coupon) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a couponUserBelongsToCouponTx) Delete(values ...*models.Coupon) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a couponUserBelongsToCouponTx) Clear() error {
return a.tx.Clear()
}
func (a couponUserBelongsToCouponTx) Count() int64 {
return a.tx.Count()
}
func (a couponUserBelongsToCouponTx) Unscoped() *couponUserBelongsToCouponTx {
a.tx = a.tx.Unscoped()
return &a
}
type couponUserBelongsToUser struct {
db *gorm.DB
field.RelationField
Admin struct {
field.RelationField
Roles struct {
field.RelationField
Permissions struct {
field.RelationField
Parent struct {
field.RelationField
}
Children struct {
field.RelationField
}
}
}
}
Discount struct {
field.RelationField
}
Roles struct {
field.RelationField
Permissions struct {
field.RelationField
}
}
}
func (a couponUserBelongsToUser) Where(conds ...field.Expr) *couponUserBelongsToUser {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a couponUserBelongsToUser) WithContext(ctx context.Context) *couponUserBelongsToUser {
a.db = a.db.WithContext(ctx)
return &a
}
func (a couponUserBelongsToUser) Session(session *gorm.Session) *couponUserBelongsToUser {
a.db = a.db.Session(session)
return &a
}
func (a couponUserBelongsToUser) Model(m *models.CouponUser) *couponUserBelongsToUserTx {
return &couponUserBelongsToUserTx{a.db.Model(m).Association(a.Name())}
}
func (a couponUserBelongsToUser) Unscoped() *couponUserBelongsToUser {
a.db = a.db.Unscoped()
return &a
}
type couponUserBelongsToUserTx struct{ tx *gorm.Association }
func (a couponUserBelongsToUserTx) Find() (result *models.User, err error) {
return result, a.tx.Find(&result)
}
func (a couponUserBelongsToUserTx) Append(values ...*models.User) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a couponUserBelongsToUserTx) Replace(values ...*models.User) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a couponUserBelongsToUserTx) Delete(values ...*models.User) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a couponUserBelongsToUserTx) Clear() error {
return a.tx.Clear()
}
func (a couponUserBelongsToUserTx) Count() int64 {
return a.tx.Count()
}
func (a couponUserBelongsToUserTx) Unscoped() *couponUserBelongsToUserTx {
a.tx = a.tx.Unscoped()
return &a
}
type couponUserDo struct{ gen.DO }
func (c couponUserDo) Debug() *couponUserDo {
return c.withDO(c.DO.Debug())
}
func (c couponUserDo) WithContext(ctx context.Context) *couponUserDo {
return c.withDO(c.DO.WithContext(ctx))
}
func (c couponUserDo) ReadDB() *couponUserDo {
return c.Clauses(dbresolver.Read)
}
func (c couponUserDo) WriteDB() *couponUserDo {
return c.Clauses(dbresolver.Write)
}
func (c couponUserDo) Session(config *gorm.Session) *couponUserDo {
return c.withDO(c.DO.Session(config))
}
func (c couponUserDo) Clauses(conds ...clause.Expression) *couponUserDo {
return c.withDO(c.DO.Clauses(conds...))
}
func (c couponUserDo) Returning(value interface{}, columns ...string) *couponUserDo {
return c.withDO(c.DO.Returning(value, columns...))
}
func (c couponUserDo) Not(conds ...gen.Condition) *couponUserDo {
return c.withDO(c.DO.Not(conds...))
}
func (c couponUserDo) Or(conds ...gen.Condition) *couponUserDo {
return c.withDO(c.DO.Or(conds...))
}
func (c couponUserDo) Select(conds ...field.Expr) *couponUserDo {
return c.withDO(c.DO.Select(conds...))
}
func (c couponUserDo) Where(conds ...gen.Condition) *couponUserDo {
return c.withDO(c.DO.Where(conds...))
}
func (c couponUserDo) Order(conds ...field.Expr) *couponUserDo {
return c.withDO(c.DO.Order(conds...))
}
func (c couponUserDo) Distinct(cols ...field.Expr) *couponUserDo {
return c.withDO(c.DO.Distinct(cols...))
}
func (c couponUserDo) Omit(cols ...field.Expr) *couponUserDo {
return c.withDO(c.DO.Omit(cols...))
}
func (c couponUserDo) Join(table schema.Tabler, on ...field.Expr) *couponUserDo {
return c.withDO(c.DO.Join(table, on...))
}
func (c couponUserDo) LeftJoin(table schema.Tabler, on ...field.Expr) *couponUserDo {
return c.withDO(c.DO.LeftJoin(table, on...))
}
func (c couponUserDo) RightJoin(table schema.Tabler, on ...field.Expr) *couponUserDo {
return c.withDO(c.DO.RightJoin(table, on...))
}
func (c couponUserDo) Group(cols ...field.Expr) *couponUserDo {
return c.withDO(c.DO.Group(cols...))
}
func (c couponUserDo) Having(conds ...gen.Condition) *couponUserDo {
return c.withDO(c.DO.Having(conds...))
}
func (c couponUserDo) Limit(limit int) *couponUserDo {
return c.withDO(c.DO.Limit(limit))
}
func (c couponUserDo) Offset(offset int) *couponUserDo {
return c.withDO(c.DO.Offset(offset))
}
func (c couponUserDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *couponUserDo {
return c.withDO(c.DO.Scopes(funcs...))
}
func (c couponUserDo) Unscoped() *couponUserDo {
return c.withDO(c.DO.Unscoped())
}
func (c couponUserDo) Create(values ...*models.CouponUser) error {
if len(values) == 0 {
return nil
}
return c.DO.Create(values)
}
func (c couponUserDo) CreateInBatches(values []*models.CouponUser, batchSize int) error {
return c.DO.CreateInBatches(values, batchSize)
}
// Save : !!! underlying implementation is different with GORM
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
func (c couponUserDo) Save(values ...*models.CouponUser) error {
if len(values) == 0 {
return nil
}
return c.DO.Save(values)
}
func (c couponUserDo) First() (*models.CouponUser, error) {
if result, err := c.DO.First(); err != nil {
return nil, err
} else {
return result.(*models.CouponUser), nil
}
}
func (c couponUserDo) Take() (*models.CouponUser, error) {
if result, err := c.DO.Take(); err != nil {
return nil, err
} else {
return result.(*models.CouponUser), nil
}
}
func (c couponUserDo) Last() (*models.CouponUser, error) {
if result, err := c.DO.Last(); err != nil {
return nil, err
} else {
return result.(*models.CouponUser), nil
}
}
func (c couponUserDo) Find() ([]*models.CouponUser, error) {
result, err := c.DO.Find()
return result.([]*models.CouponUser), err
}
func (c couponUserDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.CouponUser, err error) {
buf := make([]*models.CouponUser, 0, batchSize)
err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
defer func() { results = append(results, buf...) }()
return fc(tx, batch)
})
return results, err
}
func (c couponUserDo) FindInBatches(result *[]*models.CouponUser, batchSize int, fc func(tx gen.Dao, batch int) error) error {
return c.DO.FindInBatches(result, batchSize, fc)
}
func (c couponUserDo) Attrs(attrs ...field.AssignExpr) *couponUserDo {
return c.withDO(c.DO.Attrs(attrs...))
}
func (c couponUserDo) Assign(attrs ...field.AssignExpr) *couponUserDo {
return c.withDO(c.DO.Assign(attrs...))
}
func (c couponUserDo) Joins(fields ...field.RelationField) *couponUserDo {
for _, _f := range fields {
c = *c.withDO(c.DO.Joins(_f))
}
return &c
}
func (c couponUserDo) Preload(fields ...field.RelationField) *couponUserDo {
for _, _f := range fields {
c = *c.withDO(c.DO.Preload(_f))
}
return &c
}
func (c couponUserDo) FirstOrInit() (*models.CouponUser, error) {
if result, err := c.DO.FirstOrInit(); err != nil {
return nil, err
} else {
return result.(*models.CouponUser), nil
}
}
func (c couponUserDo) FirstOrCreate() (*models.CouponUser, error) {
if result, err := c.DO.FirstOrCreate(); err != nil {
return nil, err
} else {
return result.(*models.CouponUser), nil
}
}
func (c couponUserDo) FindByPage(offset int, limit int) (result []*models.CouponUser, count int64, err error) {
result, err = c.Offset(offset).Limit(limit).Find()
if err != nil {
return
}
if size := len(result); 0 < limit && 0 < size && size < limit {
count = int64(size + offset)
return
}
count, err = c.Offset(-1).Limit(-1).Count()
return
}
func (c couponUserDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
count, err = c.Count()
if err != nil {
return
}
err = c.Offset(offset).Limit(limit).Scan(result)
return
}
func (c couponUserDo) Scan(result interface{}) (err error) {
return c.DO.Scan(result)
}
func (c couponUserDo) Delete(models ...*models.CouponUser) (result gen.ResultInfo, err error) {
return c.DO.Delete(models)
}
func (c *couponUserDo) withDO(do gen.Dao) *couponUserDo {
c.DO = *do.(*gen.DO)
return c
}

View File

@@ -25,6 +25,7 @@ var (
Channel *channel Channel *channel
Client *client Client *client
Coupon *coupon Coupon *coupon
CouponUser *couponUser
Edge *edge Edge *edge
Inquiry *inquiry Inquiry *inquiry
LinkAdminRole *linkAdminRole LinkAdminRole *linkAdminRole
@@ -63,6 +64,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
Channel = &Q.Channel Channel = &Q.Channel
Client = &Q.Client Client = &Q.Client
Coupon = &Q.Coupon Coupon = &Q.Coupon
CouponUser = &Q.CouponUser
Edge = &Q.Edge Edge = &Q.Edge
Inquiry = &Q.Inquiry Inquiry = &Q.Inquiry
LinkAdminRole = &Q.LinkAdminRole LinkAdminRole = &Q.LinkAdminRole
@@ -102,6 +104,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
Channel: newChannel(db, opts...), Channel: newChannel(db, opts...),
Client: newClient(db, opts...), Client: newClient(db, opts...),
Coupon: newCoupon(db, opts...), Coupon: newCoupon(db, opts...),
CouponUser: newCouponUser(db, opts...),
Edge: newEdge(db, opts...), Edge: newEdge(db, opts...),
Inquiry: newInquiry(db, opts...), Inquiry: newInquiry(db, opts...),
LinkAdminRole: newLinkAdminRole(db, opts...), LinkAdminRole: newLinkAdminRole(db, opts...),
@@ -142,6 +145,7 @@ type Query struct {
Channel channel Channel channel
Client client Client client
Coupon coupon Coupon coupon
CouponUser couponUser
Edge edge Edge edge
Inquiry inquiry Inquiry inquiry
LinkAdminRole linkAdminRole LinkAdminRole linkAdminRole
@@ -183,6 +187,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
Channel: q.Channel.clone(db), Channel: q.Channel.clone(db),
Client: q.Client.clone(db), Client: q.Client.clone(db),
Coupon: q.Coupon.clone(db), Coupon: q.Coupon.clone(db),
CouponUser: q.CouponUser.clone(db),
Edge: q.Edge.clone(db), Edge: q.Edge.clone(db),
Inquiry: q.Inquiry.clone(db), Inquiry: q.Inquiry.clone(db),
LinkAdminRole: q.LinkAdminRole.clone(db), LinkAdminRole: q.LinkAdminRole.clone(db),
@@ -231,6 +236,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
Channel: q.Channel.replaceDB(db), Channel: q.Channel.replaceDB(db),
Client: q.Client.replaceDB(db), Client: q.Client.replaceDB(db),
Coupon: q.Coupon.replaceDB(db), Coupon: q.Coupon.replaceDB(db),
CouponUser: q.CouponUser.replaceDB(db),
Edge: q.Edge.replaceDB(db), Edge: q.Edge.replaceDB(db),
Inquiry: q.Inquiry.replaceDB(db), Inquiry: q.Inquiry.replaceDB(db),
LinkAdminRole: q.LinkAdminRole.replaceDB(db), LinkAdminRole: q.LinkAdminRole.replaceDB(db),
@@ -269,6 +275,7 @@ type queryCtx struct {
Channel *channelDo Channel *channelDo
Client *clientDo Client *clientDo
Coupon *couponDo Coupon *couponDo
CouponUser *couponUserDo
Edge *edgeDo Edge *edgeDo
Inquiry *inquiryDo Inquiry *inquiryDo
LinkAdminRole *linkAdminRoleDo LinkAdminRole *linkAdminRoleDo
@@ -307,6 +314,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
Channel: q.Channel.WithContext(ctx), Channel: q.Channel.WithContext(ctx),
Client: q.Client.WithContext(ctx), Client: q.Client.WithContext(ctx),
Coupon: q.Coupon.WithContext(ctx), Coupon: q.Coupon.WithContext(ctx),
CouponUser: q.CouponUser.WithContext(ctx),
Edge: q.Edge.WithContext(ctx), Edge: q.Edge.WithContext(ctx),
Inquiry: q.Inquiry.WithContext(ctx), Inquiry: q.Inquiry.WithContext(ctx),
LinkAdminRole: q.LinkAdminRole.WithContext(ctx), LinkAdminRole: q.LinkAdminRole.WithContext(ctx),

View File

@@ -128,6 +128,9 @@ func newLogsUserUsage(db *gorm.DB, opts ...gen.DOOption) logsUserUsage {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -139,6 +142,9 @@ func newLogsUserUsage(db *gorm.DB, opts ...gen.DOOption) logsUserUsage {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -147,8 +153,16 @@ func newLogsUserUsage(db *gorm.DB, opts ...gen.DOOption) logsUserUsage {
RelationField: field.NewRelation("Resource.Short.Sku", "models.ProductSku"), RelationField: field.NewRelation("Resource.Short.Sku", "models.ProductSku"),
Product: struct { Product: struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
}{ }{
RelationField: field.NewRelation("Resource.Short.Sku.Product", "models.Product"), RelationField: field.NewRelation("Resource.Short.Sku.Product", "models.Product"),
Skus: struct {
field.RelationField
}{
RelationField: field.NewRelation("Resource.Short.Sku.Product.Skus", "models.ProductSku"),
},
}, },
Discount: struct { Discount: struct {
field.RelationField field.RelationField
@@ -391,6 +405,9 @@ type logsUserUsageBelongsToResource struct {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField

View File

@@ -36,6 +36,29 @@ func newProduct(db *gorm.DB, opts ...gen.DOOption) product {
_product.Description = field.NewString(tableName, "description") _product.Description = field.NewString(tableName, "description")
_product.Sort = field.NewInt32(tableName, "sort") _product.Sort = field.NewInt32(tableName, "sort")
_product.Status = field.NewInt(tableName, "status") _product.Status = field.NewInt(tableName, "status")
_product.Skus = productHasManySkus{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Skus", "models.ProductSku"),
Product: struct {
field.RelationField
Skus struct {
field.RelationField
}
}{
RelationField: field.NewRelation("Skus.Product", "models.Product"),
Skus: struct {
field.RelationField
}{
RelationField: field.NewRelation("Skus.Product.Skus", "models.ProductSku"),
},
},
Discount: struct {
field.RelationField
}{
RelationField: field.NewRelation("Skus.Discount", "models.ProductDiscount"),
},
}
_product.fillFieldMap() _product.fillFieldMap()
@@ -55,6 +78,7 @@ type product struct {
Description field.String Description field.String
Sort field.Int32 Sort field.Int32
Status field.Int Status field.Int
Skus productHasManySkus
fieldMap map[string]field.Expr fieldMap map[string]field.Expr
} }
@@ -96,7 +120,7 @@ func (p *product) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (p *product) fillFieldMap() { func (p *product) fillFieldMap() {
p.fieldMap = make(map[string]field.Expr, 9) p.fieldMap = make(map[string]field.Expr, 10)
p.fieldMap["id"] = p.ID p.fieldMap["id"] = p.ID
p.fieldMap["created_at"] = p.CreatedAt p.fieldMap["created_at"] = p.CreatedAt
p.fieldMap["updated_at"] = p.UpdatedAt p.fieldMap["updated_at"] = p.UpdatedAt
@@ -106,18 +130,113 @@ func (p *product) fillFieldMap() {
p.fieldMap["description"] = p.Description p.fieldMap["description"] = p.Description
p.fieldMap["sort"] = p.Sort p.fieldMap["sort"] = p.Sort
p.fieldMap["status"] = p.Status p.fieldMap["status"] = p.Status
} }
func (p product) clone(db *gorm.DB) product { func (p product) clone(db *gorm.DB) product {
p.productDo.ReplaceConnPool(db.Statement.ConnPool) p.productDo.ReplaceConnPool(db.Statement.ConnPool)
p.Skus.db = db.Session(&gorm.Session{Initialized: true})
p.Skus.db.Statement.ConnPool = db.Statement.ConnPool
return p return p
} }
func (p product) replaceDB(db *gorm.DB) product { func (p product) replaceDB(db *gorm.DB) product {
p.productDo.ReplaceDB(db) p.productDo.ReplaceDB(db)
p.Skus.db = db.Session(&gorm.Session{})
return p return p
} }
type productHasManySkus struct {
db *gorm.DB
field.RelationField
Product struct {
field.RelationField
Skus struct {
field.RelationField
}
}
Discount struct {
field.RelationField
}
}
func (a productHasManySkus) Where(conds ...field.Expr) *productHasManySkus {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a productHasManySkus) WithContext(ctx context.Context) *productHasManySkus {
a.db = a.db.WithContext(ctx)
return &a
}
func (a productHasManySkus) Session(session *gorm.Session) *productHasManySkus {
a.db = a.db.Session(session)
return &a
}
func (a productHasManySkus) Model(m *models.Product) *productHasManySkusTx {
return &productHasManySkusTx{a.db.Model(m).Association(a.Name())}
}
func (a productHasManySkus) Unscoped() *productHasManySkus {
a.db = a.db.Unscoped()
return &a
}
type productHasManySkusTx struct{ tx *gorm.Association }
func (a productHasManySkusTx) Find() (result []*models.ProductSku, err error) {
return result, a.tx.Find(&result)
}
func (a productHasManySkusTx) Append(values ...*models.ProductSku) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a productHasManySkusTx) Replace(values ...*models.ProductSku) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a productHasManySkusTx) Delete(values ...*models.ProductSku) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a productHasManySkusTx) Clear() error {
return a.tx.Clear()
}
func (a productHasManySkusTx) Count() int64 {
return a.tx.Count()
}
func (a productHasManySkusTx) Unscoped() *productHasManySkusTx {
a.tx = a.tx.Unscoped()
return &a
}
type productDo struct{ gen.DO } type productDo struct{ gen.DO }
func (p productDo) Debug() *productDo { func (p productDo) Debug() *productDo {

View File

@@ -36,10 +36,35 @@ func newProductSku(db *gorm.DB, opts ...gen.DOOption) productSku {
_productSku.Code = field.NewString(tableName, "code") _productSku.Code = field.NewString(tableName, "code")
_productSku.Name = field.NewString(tableName, "name") _productSku.Name = field.NewString(tableName, "name")
_productSku.Price = field.NewField(tableName, "price") _productSku.Price = field.NewField(tableName, "price")
_productSku.PriceMin = field.NewField(tableName, "price_min")
_productSku.Status = field.NewInt32(tableName, "status")
_productSku.Sort = field.NewInt32(tableName, "sort")
_productSku.CountMin = field.NewInt32(tableName, "count_min")
_productSku.Product = productSkuBelongsToProduct{ _productSku.Product = productSkuBelongsToProduct{
db: db.Session(&gorm.Session{}), db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Product", "models.Product"), RelationField: field.NewRelation("Product", "models.Product"),
Skus: struct {
field.RelationField
Product struct {
field.RelationField
}
Discount struct {
field.RelationField
}
}{
RelationField: field.NewRelation("Product.Skus", "models.ProductSku"),
Product: struct {
field.RelationField
}{
RelationField: field.NewRelation("Product.Skus.Product", "models.Product"),
},
Discount: struct {
field.RelationField
}{
RelationField: field.NewRelation("Product.Skus.Discount", "models.ProductDiscount"),
},
},
} }
_productSku.Discount = productSkuBelongsToDiscount{ _productSku.Discount = productSkuBelongsToDiscount{
@@ -66,6 +91,10 @@ type productSku struct {
Code field.String Code field.String
Name field.String Name field.String
Price field.Field Price field.Field
PriceMin field.Field
Status field.Int32
Sort field.Int32
CountMin field.Int32
Product productSkuBelongsToProduct Product productSkuBelongsToProduct
Discount productSkuBelongsToDiscount Discount productSkuBelongsToDiscount
@@ -94,6 +123,10 @@ func (p *productSku) updateTableName(table string) *productSku {
p.Code = field.NewString(table, "code") p.Code = field.NewString(table, "code")
p.Name = field.NewString(table, "name") p.Name = field.NewString(table, "name")
p.Price = field.NewField(table, "price") p.Price = field.NewField(table, "price")
p.PriceMin = field.NewField(table, "price_min")
p.Status = field.NewInt32(table, "status")
p.Sort = field.NewInt32(table, "sort")
p.CountMin = field.NewInt32(table, "count_min")
p.fillFieldMap() p.fillFieldMap()
@@ -110,7 +143,7 @@ func (p *productSku) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
} }
func (p *productSku) fillFieldMap() { func (p *productSku) fillFieldMap() {
p.fieldMap = make(map[string]field.Expr, 11) p.fieldMap = make(map[string]field.Expr, 15)
p.fieldMap["id"] = p.ID p.fieldMap["id"] = p.ID
p.fieldMap["created_at"] = p.CreatedAt p.fieldMap["created_at"] = p.CreatedAt
p.fieldMap["updated_at"] = p.UpdatedAt p.fieldMap["updated_at"] = p.UpdatedAt
@@ -120,6 +153,10 @@ func (p *productSku) fillFieldMap() {
p.fieldMap["code"] = p.Code p.fieldMap["code"] = p.Code
p.fieldMap["name"] = p.Name p.fieldMap["name"] = p.Name
p.fieldMap["price"] = p.Price p.fieldMap["price"] = p.Price
p.fieldMap["price_min"] = p.PriceMin
p.fieldMap["status"] = p.Status
p.fieldMap["sort"] = p.Sort
p.fieldMap["count_min"] = p.CountMin
} }
@@ -143,6 +180,16 @@ type productSkuBelongsToProduct struct {
db *gorm.DB db *gorm.DB
field.RelationField field.RelationField
Skus struct {
field.RelationField
Product struct {
field.RelationField
}
Discount struct {
field.RelationField
}
}
} }
func (a productSkuBelongsToProduct) Where(conds ...field.Expr) *productSkuBelongsToProduct { func (a productSkuBelongsToProduct) Where(conds ...field.Expr) *productSkuBelongsToProduct {

View File

@@ -115,8 +115,16 @@ func newProductSkuUser(db *gorm.DB, opts ...gen.DOOption) productSkuUser {
RelationField: field.NewRelation("ProductSku", "models.ProductSku"), RelationField: field.NewRelation("ProductSku", "models.ProductSku"),
Product: struct { Product: struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
}{ }{
RelationField: field.NewRelation("ProductSku.Product", "models.Product"), RelationField: field.NewRelation("ProductSku.Product", "models.Product"),
Skus: struct {
field.RelationField
}{
RelationField: field.NewRelation("ProductSku.Product.Skus", "models.ProductSku"),
},
}, },
Discount: struct { Discount: struct {
field.RelationField field.RelationField
@@ -331,6 +339,9 @@ type productSkuUserBelongsToProductSku struct {
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField

View File

@@ -153,6 +153,9 @@ func newProxy(db *gorm.DB, opts ...gen.DOOption) proxy {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -181,6 +184,9 @@ func newProxy(db *gorm.DB, opts ...gen.DOOption) proxy {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -192,6 +198,9 @@ func newProxy(db *gorm.DB, opts ...gen.DOOption) proxy {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -200,8 +209,16 @@ func newProxy(db *gorm.DB, opts ...gen.DOOption) proxy {
RelationField: field.NewRelation("Channels.Resource.Short.Sku", "models.ProductSku"), RelationField: field.NewRelation("Channels.Resource.Short.Sku", "models.ProductSku"),
Product: struct { Product: struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
}{ }{
RelationField: field.NewRelation("Channels.Resource.Short.Sku.Product", "models.Product"), RelationField: field.NewRelation("Channels.Resource.Short.Sku.Product", "models.Product"),
Skus: struct {
field.RelationField
}{
RelationField: field.NewRelation("Channels.Resource.Short.Sku.Product.Skus", "models.ProductSku"),
},
}, },
Discount: struct { Discount: struct {
field.RelationField field.RelationField
@@ -387,6 +404,9 @@ type proxyHasManyChannels struct {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField

View File

@@ -44,6 +44,9 @@ func newResource(db *gorm.DB, opts ...gen.DOOption) resource {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField
@@ -52,8 +55,16 @@ func newResource(db *gorm.DB, opts ...gen.DOOption) resource {
RelationField: field.NewRelation("Short.Sku", "models.ProductSku"), RelationField: field.NewRelation("Short.Sku", "models.ProductSku"),
Product: struct { Product: struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
}{ }{
RelationField: field.NewRelation("Short.Sku.Product", "models.Product"), RelationField: field.NewRelation("Short.Sku.Product", "models.Product"),
Skus: struct {
field.RelationField
}{
RelationField: field.NewRelation("Short.Sku.Product.Skus", "models.ProductSku"),
},
}, },
Discount: struct { Discount: struct {
field.RelationField field.RelationField
@@ -266,6 +277,9 @@ type resourceHasOneShort struct {
field.RelationField field.RelationField
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField

View File

@@ -43,8 +43,16 @@ func newResourceLong(db *gorm.DB, opts ...gen.DOOption) resourceLong {
RelationField: field.NewRelation("Sku", "models.ProductSku"), RelationField: field.NewRelation("Sku", "models.ProductSku"),
Product: struct { Product: struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
}{ }{
RelationField: field.NewRelation("Sku.Product", "models.Product"), RelationField: field.NewRelation("Sku.Product", "models.Product"),
Skus: struct {
field.RelationField
}{
RelationField: field.NewRelation("Sku.Product.Skus", "models.ProductSku"),
},
}, },
Discount: struct { Discount: struct {
field.RelationField field.RelationField
@@ -149,6 +157,9 @@ type resourceLongHasOneSku struct {
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField

View File

@@ -43,8 +43,16 @@ func newResourceShort(db *gorm.DB, opts ...gen.DOOption) resourceShort {
RelationField: field.NewRelation("Sku", "models.ProductSku"), RelationField: field.NewRelation("Sku", "models.ProductSku"),
Product: struct { Product: struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
}{ }{
RelationField: field.NewRelation("Sku.Product", "models.Product"), RelationField: field.NewRelation("Sku.Product", "models.Product"),
Skus: struct {
field.RelationField
}{
RelationField: field.NewRelation("Sku.Product.Skus", "models.ProductSku"),
},
}, },
Discount: struct { Discount: struct {
field.RelationField field.RelationField
@@ -149,6 +157,9 @@ type resourceShortHasOneSku struct {
Product struct { Product struct {
field.RelationField field.RelationField
Skus struct {
field.RelationField
}
} }
Discount struct { Discount struct {
field.RelationField field.RelationField

View File

@@ -25,7 +25,6 @@ func ApplyRouters(app *fiber.App) {
if env.RunMode == env.RunModeDev { if env.RunMode == env.RunModeDev {
debug := app.Group("/debug") debug := app.Group("/debug")
debug.Get("/sms/:phone", handlers.DebugGetSmsCode) debug.Get("/sms/:phone", handlers.DebugGetSmsCode)
debug.Get("/proxy/register", handlers.DebugRegisterProxyBaiYin)
debug.Get("/iden/clear/:phone", handlers.DebugIdentifyClear) debug.Get("/iden/clear/:phone", handlers.DebugIdentifyClear)
debug.Get("/session/now", func(ctx *fiber.Ctx) error { debug.Get("/session/now", func(ctx *fiber.Ctx) error {
rs, err := q.Session.Where(q.Session.AccessTokenExpires.Gt(time.Now())).Find() rs, err := q.Session.Where(q.Session.AccessTokenExpires.Gt(time.Now())).Find()
@@ -109,6 +108,14 @@ func userRouter(api fiber.Router) {
// 前台 // 前台
inquiry := api.Group("/inquiry") inquiry := api.Group("/inquiry")
inquiry.Post("/create", handlers.CreateInquiry) inquiry.Post("/create", handlers.CreateInquiry)
// 产品
product := api.Group("/product")
product.Post("/list", handlers.AllProduct)
// 认证
verify := api.Group("/verify")
verify.Post("/sms/password", handlers.SendSmsCodeForPassword)
} }
// 客户端接口路由 // 客户端接口路由
@@ -116,7 +123,7 @@ func clientRouter(api fiber.Router) {
client := api client := api
// 验证短信令牌 // 验证短信令牌
client.Post("/verify/sms", handlers.SmsCode) client.Post("/verify/sms", handlers.SendSmsCode)
// 套餐定价查询 // 套餐定价查询
resource := client.Group("/resource") resource := client.Group("/resource")
@@ -126,9 +133,6 @@ func clientRouter(api fiber.Router) {
channel := client.Group("/channel") channel := client.Group("/channel")
channel.Post("/remove", handlers.RemoveChannels) channel.Post("/remove", handlers.RemoveChannels)
// 代理网关注册
proxy := client.Group("/proxy")
proxy.Post("/register/baidyin", handlers.ProxyRegisterBaiYin)
} }
// 管理员接口路由 // 管理员接口路由
@@ -159,35 +163,59 @@ func adminRouter(api fiber.Router) {
// user 用户 // user 用户
var user = api.Group("/user") var user = api.Group("/user")
user.Post("/page", handlers.PageUserByAdmin) user.Post("/page", handlers.PageUserByAdmin)
user.Post("/page/not-bind", handlers.PageUserNotBindByAdmin)
user.Post("/get", handlers.GetUserByAdmin) user.Post("/get", handlers.GetUserByAdmin)
user.Post("/create", handlers.CreateUserByAdmin) user.Post("/create", handlers.CreateUserByAdmin)
user.Post("/update", handlers.UpdateUserByAdmin) user.Post("/update", handlers.UpdateUserByAdmin)
user.Post("/remove", handlers.RemoveUserByAdmin) user.Post("/remove", handlers.RemoveUserByAdmin)
user.Post("/bind", handlers.BindAdmin) user.Post("/update/bind", handlers.BindAdmin)
user.Post("/update/balance", handlers.UpdateUserBalanceByAdmin) user.Post("/update/balance", handlers.UpdateUserBalanceByAdmin)
user.Post("/update/balance-inc", handlers.UpdateUserBalanceIncByAdmin)
user.Post("/update/balance-dec", handlers.UpdateUserBalanceDecByAdmin)
// resource 套餐 // resource 套餐
var resource = api.Group("/resource") var resource = api.Group("/resource")
resource.Post("/short/page", handlers.PageResourceShortByAdmin) resource.Post("/short/page", handlers.PageResourceShortByAdmin)
resource.Post("/short/page/of-user", handlers.PageResourceShortOfUserByAdmin)
resource.Post("/long/page", handlers.PageResourceLongByAdmin) resource.Post("/long/page", handlers.PageResourceLongByAdmin)
resource.Post("/long/page/of-user", handlers.PageResourceLongOfUserByAdmin)
resource.Post("/update", handlers.UpdateResourceByAdmin) resource.Post("/update", handlers.UpdateResourceByAdmin)
// batch 批次 // batch 批次
var batch = api.Group("/batch") var batch = api.Group("/batch")
batch.Post("/page", handlers.PageBatchByAdmin) batch.Post("/page", handlers.PageBatchByAdmin)
batch.Post("/page/of-user", handlers.PageBatchOfUserByAdmin)
// channel 通道 // channel 通道
var channel = api.Group("/channel") var channel = api.Group("/channel")
channel.Post("/page", handlers.PageChannelByAdmin) channel.Post("/page", handlers.PageChannelByAdmin)
channel.Post("/page/of-user", handlers.PageChannelOfUserByAdmin)
// proxy 代理
var proxy = api.Group("/proxy")
proxy.Post("/all", handlers.AllProxyByAdmin)
proxy.Post("/page", handlers.PageProxyByAdmin)
proxy.Post("/create", handlers.CreateProxy)
proxy.Post("/update", handlers.UpdateProxy)
proxy.Post("/update/status", handlers.UpdateProxyStatus)
proxy.Post("/remove", handlers.RemoveProxy)
// trade 交易 // trade 交易
var trade = api.Group("/trade") var trade = api.Group("/trade")
trade.Post("/page", handlers.PageTradeByAdmin) trade.Post("/page", handlers.PageTradeByAdmin)
trade.Post("/page/of-user", handlers.PageTradeOfUserByAdmin)
trade.Post("/complete", handlers.TradeCompleteByAdmin)
// bill 账单 // bill 账单
var bill = api.Group("/bill") var bill = api.Group("/bill")
bill.Post("/page", handlers.PageBillByAdmin) bill.Post("/page", handlers.PageBillByAdmin)
bill.Post("/page/of-user", handlers.PageBillOfUserByAdmin)
// balance-activity 余额变动
var balanceActivity = api.Group("/balance-activity")
balanceActivity.Post("/page", handlers.PageBalanceActivityByAdmin)
balanceActivity.Post("/page/of-user", handlers.PageBalanceActivityOfUserByAdmin)
// product 产品 // product 产品
var product = api.Group("/product") var product = api.Group("/product")
@@ -200,6 +228,7 @@ func adminRouter(api fiber.Router) {
product.Post("/sku/page", handlers.PageProductSkuByAdmin) product.Post("/sku/page", handlers.PageProductSkuByAdmin)
product.Post("/sku/create", handlers.CreateProductSku) product.Post("/sku/create", handlers.CreateProductSku)
product.Post("/sku/update", handlers.UpdateProductSku) product.Post("/sku/update", handlers.UpdateProductSku)
product.Post("/sku/update/status", handlers.UpdateProductStatusSku)
product.Post("/sku/remove", handlers.DeleteProductSku) product.Post("/sku/remove", handlers.DeleteProductSku)
product.Post("/sku/update/discount/batch", handlers.BatchUpdateProductSkuDiscount) product.Post("/sku/update/discount/batch", handlers.BatchUpdateProductSkuDiscount)

View File

@@ -15,7 +15,7 @@ var Admin = &adminService{}
type adminService struct{} type adminService struct{}
func (s *adminService) PageAdmins(req core.PageReq) (result []*m.Admin, count int64, err error) { func (s *adminService) Page(req core.PageReq) (result []*m.Admin, count int64, err error) {
return q.Admin. return q.Admin.
Preload(q.Admin.Roles). Preload(q.Admin.Roles).
Omit(q.Admin.Password). Omit(q.Admin.Password).
@@ -30,25 +30,14 @@ func (s *adminService) All() (result []*m.Admin, err error) {
Find() Find()
} }
type CreateAdmin struct { func (s *adminService) Create(create *CreateAdmin) error {
Username string `json:"username" validate:"required,min=3,max=50"`
Password string `json:"password" validate:"required,min=6,max=50"`
Name *string `json:"name"`
Avatar *string `json:"avatar"`
Phone *string `json:"phone"`
Email *string `json:"email"`
Status *m.AdminStatus `json:"status"`
Roles []int32 `json:"roles"`
}
func (s *adminService) CreateAdmin(create *CreateAdmin) error {
// 哈希密码 // 哈希密码
hash, err := bcrypt.GenerateFromPassword([]byte(create.Password), bcrypt.DefaultCost) hash, err := bcrypt.GenerateFromPassword([]byte(create.Password), bcrypt.DefaultCost)
if err != nil { if err != nil {
return core.NewServErr("密码加密失败", err) return core.NewServErr("密码加密失败", err)
} }
return q.Q.Transaction(func(tx *q.Query) error { return q.Q.Transaction(func(q *q.Query) error {
// 创建管理员 // 创建管理员
admin := &m.Admin{ admin := &m.Admin{
Username: create.Username, Username: create.Username,
@@ -59,7 +48,7 @@ func (s *adminService) CreateAdmin(create *CreateAdmin) error {
Email: create.Email, Email: create.Email,
Status: u.Else(create.Status, m.AdminStatusEnabled), Status: u.Else(create.Status, m.AdminStatusEnabled),
} }
if err := tx.Admin.Create(admin); err != nil { if err := q.Admin.Create(admin); err != nil {
return err return err
} }
@@ -72,7 +61,7 @@ func (s *adminService) CreateAdmin(create *CreateAdmin) error {
RoleID: roleID, RoleID: roleID,
} }
} }
if err := tx.LinkAdminRole.CreateInBatches(links, 1000); err != nil { if err := q.LinkAdminRole.CreateInBatches(links, 1000); err != nil {
return err return err
} }
} }
@@ -81,18 +70,18 @@ func (s *adminService) CreateAdmin(create *CreateAdmin) error {
}) })
} }
type UpdateAdmin struct { type CreateAdmin struct {
Id int32 `json:"id" validate:"required"` Username string `json:"username" validate:"required,min=3,max=50"`
Password *string `json:"password"` Password string `json:"password" validate:"required,min=6,max=50"`
Name *string `json:"name"` Name *string `json:"name"`
Avatar *string `json:"avatar"` Avatar *string `json:"avatar"`
Phone *string `json:"phone"` Phone *string `json:"phone"`
Email *string `json:"email"` Email *string `json:"email"`
Status *m.AdminStatus `json:"status"` Status *m.AdminStatus `json:"status"`
Roles *[]int32 `json:"roles"` Roles []int32 `json:"roles"`
} }
func (s *adminService) UpdateAdmin(update *UpdateAdmin) error { func (s *adminService) Update(update *UpdateAdmin) error {
simples := make([]field.AssignExpr, 0) simples := make([]field.AssignExpr, 0)
if update.Password != nil { if update.Password != nil {
@@ -118,11 +107,14 @@ func (s *adminService) UpdateAdmin(update *UpdateAdmin) error {
simples = append(simples, q.Admin.Status.Value(int(*update.Status))) simples = append(simples, q.Admin.Status.Value(int(*update.Status)))
} }
return q.Q.Transaction(func(tx *q.Query) error { return q.Q.Transaction(func(q *q.Query) error {
// 更新管理员基本信息 // 更新管理员基本信息
if len(simples) > 0 { if len(simples) > 0 {
_, err := tx.Admin. _, err := q.Admin.
Where(tx.Admin.ID.Eq(update.Id), tx.Admin.Username.Neq("admin")). Where(
q.Admin.ID.Eq(update.Id),
q.Admin.Lock.Is(false),
).
UpdateSimple(simples...) UpdateSimple(simples...)
if err != nil { if err != nil {
return err return err
@@ -132,7 +124,7 @@ func (s *adminService) UpdateAdmin(update *UpdateAdmin) error {
// 更新角色关联 // 更新角色关联
if update.Roles != nil { if update.Roles != nil {
roles := *update.Roles roles := *update.Roles
if _, err := tx.LinkAdminRole.Where(tx.LinkAdminRole.AdminID.Eq(update.Id)).Delete(); err != nil { if _, err := q.LinkAdminRole.Where(q.LinkAdminRole.AdminID.Eq(update.Id)).Delete(); err != nil {
return err return err
} }
if len(roles) > 0 { if len(roles) > 0 {
@@ -143,7 +135,7 @@ func (s *adminService) UpdateAdmin(update *UpdateAdmin) error {
RoleID: roleID, RoleID: roleID,
} }
} }
if err := tx.LinkAdminRole.CreateInBatches(links, 1000); err != nil { if err := q.LinkAdminRole.CreateInBatches(links, 1000); err != nil {
return err return err
} }
} }
@@ -153,7 +145,23 @@ func (s *adminService) UpdateAdmin(update *UpdateAdmin) error {
}) })
} }
func (s *adminService) RemoveAdmin(id int32) error { type UpdateAdmin struct {
_, err := q.Admin.Where(q.Admin.ID.Eq(id), q.Admin.Username.Neq("admin")).UpdateColumn(q.Admin.DeletedAt, time.Now()) Id int32 `json:"id" validate:"required"`
Password *string `json:"password"`
Name *string `json:"name"`
Avatar *string `json:"avatar"`
Phone *string `json:"phone"`
Email *string `json:"email"`
Status *m.AdminStatus `json:"status"`
Roles *[]int32 `json:"roles"`
}
func (s *adminService) Remove(id int32) error {
_, err := q.Admin.
Where(
q.Admin.ID.Eq(id),
q.Admin.Lock.Is(false),
).
UpdateColumn(q.Admin.DeletedAt, time.Now())
return err return err
} }

View File

@@ -9,28 +9,42 @@ var Bill = &billService{}
type billService struct{} type billService struct{}
func (s *billService) CreateForBalance(q *q.Query, uid, tradeId int32, detail *TradeDetail) error { func (s *billService) CreateForBalance(q *q.Query, uid, tradeId int32, detail *TradeDetail) (*m.Bill, error) {
return q.Bill.Create(&m.Bill{ bill := &m.Bill{
UserID: uid, UserID: uid,
BillNo: ID.GenReadable("bil"), BillNo: ID.GenReadable("bil"),
TradeID: &tradeId, TradeID: &tradeId,
Type: m.BillTypeRecharge, Type: m.BillTypeRecharge,
Info: &detail.Subject, Info: &detail.Subject,
Amount: detail.Amount, Amount: detail.Discounted,
Actual: detail.Actual, Actual: detail.Actual,
}) }
err := q.Bill.Create(bill)
if err != nil {
return nil, err
}
return bill, nil
} }
func (s *billService) CreateForResource(q *q.Query, uid, resourceId int32, tradeId *int32, detail *TradeDetail) error { func (s *billService) CreateForResource(q *q.Query, uid, resourceId int32, tradeId *int32, detail *TradeDetail) (*m.Bill, error) {
return q.Bill.Create(&m.Bill{ bill := &m.Bill{
UserID: uid, UserID: uid,
BillNo: ID.GenReadable("bil"), BillNo: ID.GenReadable("bil"),
ResourceID: &resourceId, ResourceID: &resourceId,
TradeID: tradeId, TradeID: tradeId,
CouponID: detail.CouponId, CouponUserID: detail.CouponUserId,
Type: m.BillTypeConsume, Type: m.BillTypeConsume,
Info: &detail.Subject, Info: &detail.Subject,
Amount: detail.Amount, Amount: detail.Discounted,
Actual: detail.Actual, Actual: detail.Actual,
}) }
err := q.Bill.Create(bill)
if err != nil {
return nil, err
}
return bill, nil
} }

View File

@@ -24,7 +24,7 @@ var Channel = &channelServer{
} }
type ChannelServiceProvider interface { type ChannelServiceProvider interface {
CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error)
RemoveChannels(batch string) error RemoveChannels(batch string) error
} }
@@ -32,8 +32,8 @@ type channelServer struct {
provider ChannelServiceProvider provider ChannelServiceProvider
} }
func (s *channelServer) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error) { func (s *channelServer) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error) {
return s.provider.CreateChannels(source, resourceId, authWhitelist, authPassword, count, edgeFilter...) return s.provider.CreateChannels(source, resourceId, authWhitelist, authPassword, count, edgeFilter)
} }
func (s *channelServer) RemoveChannels(batch string) error { func (s *channelServer) RemoveChannels(batch string) error {
@@ -94,7 +94,7 @@ func findResource(resourceId int32, now time.Time) (*ResourceView, error) {
var sub = resource.Short var sub = resource.Short
info.ShortId = &sub.ID info.ShortId = &sub.ID
info.ExpireAt = sub.ExpireAt info.ExpireAt = sub.ExpireAt
info.Live = time.Duration(sub.Live) * time.Second info.Live = time.Duration(sub.Live) * time.Minute
info.Mode = sub.Type info.Mode = sub.Type
info.Quota = sub.Quota info.Quota = sub.Quota
info.Used = sub.Used info.Used = sub.Used
@@ -232,7 +232,7 @@ func regChans(proxy int32, chans []netip.AddrPort) error {
// 缩容通道 // 缩容通道
func remChans(proxy int32) error { func remChans(proxy int32) error {
key := freeChansKey + ":" + strconv.Itoa(int(proxy)) key := freeChansKey + ":" + strconv.Itoa(int(proxy))
err := g.Redis.SRem(context.Background(), key).Err() err := g.Redis.Del(context.Background(), key).Err()
if err != nil { if err != nil {
return fmt.Errorf("缩容通道失败: %w", err) return fmt.Errorf("缩容通道失败: %w", err)
} }
@@ -272,7 +272,8 @@ local free_key = KEYS[1]
local batch_key = KEYS[2] local batch_key = KEYS[2]
local count = tonumber(ARGV[1]) local count = tonumber(ARGV[1])
if redis.call("SCARD", free_key) < count then local free_count = redis.call("SCARD", free_key)
if count <= 0 or free_count < count then
return nil return nil
end end
@@ -305,12 +306,13 @@ local free_key = KEYS[1]
local batch_key = KEYS[2] local batch_key = KEYS[2]
local chans = redis.call("LRANGE", batch_key, 0, -1) local chans = redis.call("LRANGE", batch_key, 0, -1)
redis.call("DEL", batch_key) if #chans == 0 then
return 1
if redis.call("EXISTS", free_key) == 1 then
redis.call("SADD", free_key, unpack(chans))
end end
redis.call("SADD", free_key, unpack(chans))
redis.call("DEL", batch_key)
return 1 return 1
`) `)

View File

@@ -23,10 +23,9 @@ import (
type channelBaiyinProvider struct{} type channelBaiyinProvider struct{}
func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error) { func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, filter *EdgeFilter) ([]*m.Channel, error) {
var filter *EdgeFilter = nil if filter == nil {
if len(edgeFilter) > 0 { return nil, core.NewBizErr("缺少节点过滤条件")
filter = &edgeFilter[0]
} }
now := time.Now() now := time.Now()
@@ -64,10 +63,27 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
} }
proxy := proxyResult.Proxy proxy := proxyResult.Proxy
// 获取可用通道 // 锁内确认状态并锁定端口,避免与状态切换并发穿透
chans, err := lockChans(proxy.ID, batch, count) var chans []netip.AddrPort
err = g.Redsync.WithLock(proxyStatusLockKey(proxy.ID), func() error {
lockedProxy, err := q.Proxy.Where(q.Proxy.ID.Eq(proxy.ID)).Take()
if err != nil { if err != nil {
return nil, core.NewBizErr("无可用通道,请稍后再试", err) return err
}
if lockedProxy.Status != m.ProxyStatusOnline {
return core.NewBizErr("无可用主机,请稍后再试")
}
chans, err = lockChans(proxy.ID, batch, count)
if err != nil {
return core.NewBizErr("无可用通道,请稍后再试", err)
}
proxy = *lockedProxy
return nil
})
if err != nil {
return nil, err
} }
// 获取可用节点 // 获取可用节点
@@ -86,7 +102,7 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
return nil, core.NewBizErr("获取可用节点失败", err) return nil, core.NewBizErr("获取可用节点失败", err)
} }
if edgesResp.Total != count && len(edgesResp.Edges) != count { if edgesResp.Total != count && len(edgesResp.Edges) != count {
return nil, core.NewBizErr("地区可用节点数量不足 [%s, %s] [%s]") return nil, core.NewBizErr("地区可用节点数量不足")
} }
edges := edgesResp.Edges edges := edgesResp.Edges
@@ -98,10 +114,6 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
ch := chans[i] ch := chans[i]
edge := edges[i] edge := edges[i]
if err != nil {
return nil, core.NewBizErr("解析通道地址失败", err)
}
// 通道数据 // 通道数据
channels[i] = &m.Channel{ channels[i] = &m.Channel{
UserID: user.ID, UserID: user.ID,
@@ -229,7 +241,7 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
// 提交配置 // 提交配置
secret := strings.Split(u.Z(proxy.Secret), ":") secret := strings.Split(u.Z(proxy.Secret), ":")
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1]) gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
if env.DebugExternalChange { if env.RunMode == env.RunModeProd {
// 连接节点到网关 // 连接节点到网关
err = g.Cloud.CloudConnect(&g.CloudConnectReq{ err = g.Cloud.CloudConnect(&g.CloudConnectReq{
@@ -292,7 +304,8 @@ func (s *channelBaiyinProvider) RemoveChannels(batch string) error {
} }
// 提交配置 // 提交配置
if env.DebugExternalChange { if env.RunMode == env.RunModeProd {
// 断开节点连接 // 断开节点连接
g.Cloud.CloudDisconnect(&g.CloudDisconnectReq{ g.Cloud.CloudDisconnect(&g.CloudDisconnectReq{
Uuid: proxy.Mac, Uuid: proxy.Mac,
@@ -300,7 +313,7 @@ func (s *channelBaiyinProvider) RemoveChannels(batch string) error {
}) })
// 清空通道配置 // 清空通道配置
secret := strings.Split(*proxy.Secret, ":") secret := strings.Split(u.Z(proxy.Secret), ":")
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1]) gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
err := gateway.GatewayPortConfigs(configs) err := gateway.GatewayPortConfigs(configs)
if err != nil { if err != nil {
@@ -320,6 +333,6 @@ func (s *channelBaiyinProvider) RemoveChannels(batch string) error {
return err return err
} }
slog.Debug("清除代理端口配置", "time", time.Since(start).String()) slog.Debug("清除代理端口配置", "duration", time.Since(start).String())
return nil return nil
} }

View File

@@ -3,6 +3,7 @@ package services
import ( import (
"errors" "errors"
"fmt" "fmt"
"platform/pkg/u"
"platform/web/core" "platform/web/core"
m "platform/web/models" m "platform/web/models"
q "platform/web/queries" q "platform/web/queries"
@@ -27,36 +28,32 @@ func (s *couponService) Page(req *core.PageReq) (result []*m.Coupon, count int64
func (s *couponService) Create(data CreateCouponData) error { func (s *couponService) Create(data CreateCouponData) error {
return q.Coupon.Create(&m.Coupon{ return q.Coupon.Create(&m.Coupon{
UserID: data.UserID, Name: data.Name,
Code: data.Code,
Remark: data.Remark,
Amount: data.Amount, Amount: data.Amount,
MinAmount: data.MinAmount, MinAmount: data.MinAmount,
Status: m.CouponStatusUnused, Count: int32(u.Else(data.Count, 1)),
Status: u.Else(data.Status, m.CouponStatusEnabled),
ExpireType: u.Else(data.ExpireType, m.CouponExpireTypeNever),
ExpireAt: data.ExpireAt, ExpireAt: data.ExpireAt,
ExpireIn: data.ExpireIn,
}) })
} }
type CreateCouponData struct { type CreateCouponData struct {
UserID *int32 `json:"user_id"` Name string `json:"name" validate:"required"`
Code string `json:"code" validate:"required"`
Remark *string `json:"remark"`
Amount decimal.Decimal `json:"amount" validate:"required"` Amount decimal.Decimal `json:"amount" validate:"required"`
MinAmount decimal.Decimal `json:"min_amount"` MinAmount decimal.Decimal `json:"min_amount"`
Count *int `json:"count"`
Status *m.CouponStatus `json:"status"`
ExpireType *m.CouponExpireType `json:"expire_type"`
ExpireAt *time.Time `json:"expire_at"` ExpireAt *time.Time `json:"expire_at"`
ExpireIn *int `json:"expire_in"`
} }
func (s *couponService) Update(data UpdateCouponData) error { func (s *couponService) Update(data UpdateCouponData) error {
do := make([]field.AssignExpr, 0) do := make([]field.AssignExpr, 0)
if data.Name != nil {
if data.UserID != nil { do = append(do, q.Coupon.Name.Value(*data.Name))
do = append(do, q.Coupon.UserID.Value(*data.UserID))
}
if data.Code != nil {
do = append(do, q.Coupon.Code.Value(*data.Code))
}
if data.Remark != nil {
do = append(do, q.Coupon.Remark.Value(*data.Remark))
} }
if data.Amount != nil { if data.Amount != nil {
do = append(do, q.Coupon.Amount.Value(*data.Amount)) do = append(do, q.Coupon.Amount.Value(*data.Amount))
@@ -64,12 +61,21 @@ func (s *couponService) Update(data UpdateCouponData) error {
if data.MinAmount != nil { if data.MinAmount != nil {
do = append(do, q.Coupon.MinAmount.Value(*data.MinAmount)) do = append(do, q.Coupon.MinAmount.Value(*data.MinAmount))
} }
if data.Count != nil {
do = append(do, q.Coupon.Count_.Value(int32(*data.Count)))
}
if data.Status != nil { if data.Status != nil {
do = append(do, q.Coupon.Status.Value(int(*data.Status))) do = append(do, q.Coupon.Status.Value(int(*data.Status)))
} }
if data.ExpireType != nil {
do = append(do, q.Coupon.ExpireType.Value(int(*data.ExpireType)))
}
if data.ExpireAt != nil { if data.ExpireAt != nil {
do = append(do, q.Coupon.ExpireAt.Value(*data.ExpireAt)) do = append(do, q.Coupon.ExpireAt.Value(*data.ExpireAt))
} }
if data.ExpireIn != nil {
do = append(do, q.Coupon.ExpireIn.Value(*data.ExpireIn))
}
_, err := q.Coupon.Where(q.Coupon.ID.Eq(data.ID)).UpdateSimple(do...) _, err := q.Coupon.Where(q.Coupon.ID.Eq(data.ID)).UpdateSimple(do...)
return err return err
@@ -77,13 +83,14 @@ func (s *couponService) Update(data UpdateCouponData) error {
type UpdateCouponData struct { type UpdateCouponData struct {
ID int32 `json:"id" validate:"required"` ID int32 `json:"id" validate:"required"`
UserID *int32 `json:"user_id"` Name *string `json:"name"`
Code *string `json:"code"`
Remark *string `json:"remark"`
Amount *decimal.Decimal `json:"amount"` Amount *decimal.Decimal `json:"amount"`
MinAmount *decimal.Decimal `json:"min_amount"` MinAmount *decimal.Decimal `json:"min_amount"`
Count *int `json:"count"`
Status *m.CouponStatus `json:"status"` Status *m.CouponStatus `json:"status"`
ExpireType *m.CouponExpireType `json:"expire_type"`
ExpireAt *time.Time `json:"expire_at"` ExpireAt *time.Time `json:"expire_at"`
ExpireIn *int `json:"expire_in"`
} }
func (s *couponService) Delete(id int32) error { func (s *couponService) Delete(id int32) error {
@@ -91,14 +98,14 @@ func (s *couponService) Delete(id int32) error {
return err return err
} }
func (s *couponService) GetCouponAvailableByCode(code string, amount decimal.Decimal, uid *int32) (*m.Coupon, error) { // GetUserCoupon 获取用户的指定优惠券
func (s *couponService) GetUserCoupon(uid int32, cuid int32, amount decimal.Decimal) (*m.CouponUser, error) {
// 获取优惠券 // 获取优惠券
coupon, err := q.Coupon.Where( assigned, err := q.CouponUser.Joins(q.CouponUser.Coupon).Where(
q.Coupon.Code.Eq(code), q.CouponUser.ID.Eq(cuid),
q.Coupon.Status.Eq(int(m.CouponStatusUnused)), q.CouponUser.UserID.Eq(uid),
q.Coupon. q.CouponUser.Status.Eq(int(m.CouponUserStatusUnused)),
Where(q.Coupon.ExpireAt.Gt(time.Now())). q.CouponUser.ExpireAt.Gt(time.Now()),
Or(q.Coupon.ExpireAt.IsNull()),
).Take() ).Take()
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, core.NewBizErr("优惠券不存在或已失效") return nil, core.NewBizErr("优惠券不存在或已失效")
@@ -108,32 +115,22 @@ func (s *couponService) GetCouponAvailableByCode(code string, amount decimal.Dec
} }
// 检查最小使用额度 // 检查最小使用额度
if amount.Cmp(coupon.MinAmount) < 0 { if amount.Cmp(assigned.Coupon.MinAmount) < 0 {
return nil, core.NewBizErr(fmt.Sprintf("使用此优惠券的最小额度为 %s", coupon.MinAmount)) return nil, core.NewBizErr(fmt.Sprintf("使用此优惠券的最小额度为 %s", assigned.Coupon.MinAmount))
} }
// 检查所属 return assigned, nil
if coupon.UserID != nil {
if uid == nil {
return nil, core.NewBizErr("检查优惠券所属用户失败")
}
if *coupon.UserID != *uid {
return nil, core.NewBizErr("优惠券不属于当前用户")
}
}
return coupon, nil
} }
func (s *couponService) UseCoupon(q *q.Query, id int32) error { func (s *couponService) UseCoupon(q *q.Query, cuid int32) error {
_, err := q.Coupon. _, err := q.CouponUser.
Where( Where(
q.Coupon.ID.Eq(id), q.CouponUser.ID.Eq(cuid),
q.Coupon.Status.Eq(int(m.CouponStatusUnused)), q.CouponUser.Status.Eq(int(m.CouponUserStatusUnused)),
q.Coupon.ExpireAt.Gt(time.Now()),
). ).
UpdateSimple( UpdateSimple(
q.Coupon.Status.Value(int(m.CouponStatusUsed)), q.CouponUser.Status.Value(int(m.CouponUserStatusUsed)),
q.CouponUser.UsedAt.Value(time.Now()),
) )
return err return err
} }

View File

@@ -13,11 +13,6 @@ var Product = &productService{}
type productService struct{} type productService struct{}
// 获取产品价格
func (s *productService) GetPrice(code string) {
q.ProductSku.Where(q.ProductSku.Code.Eq(code)).Find()
}
// 获取所有产品 // 获取所有产品
func (s *productService) AllProducts() ([]*m.Product, error) { func (s *productService) AllProducts() ([]*m.Product, error) {
return q.Product. return q.Product.
@@ -25,6 +20,62 @@ func (s *productService) AllProducts() ([]*m.Product, error) {
Find() Find()
} }
func (s *productService) AllProductSaleInfos() ([]*m.Product, error) {
products, err := q.Product.
Select(
q.Product.ID,
q.Product.Code,
q.Product.Name,
q.Product.Description,
q.Product.Sort,
).
Where(
q.Product.Status.Eq(int(m.ProductStatusEnabled)),
).
Order(q.Product.Sort).
Find()
if err != nil {
return nil, err
}
pids := make([]int32, len(products))
for i, p := range products {
pids[i] = p.ID
}
skus, err := q.ProductSku.
Select(
q.ProductSku.ID,
q.ProductSku.ProductID,
q.ProductSku.Name,
q.ProductSku.Code,
q.ProductSku.Price,
q.ProductSku.CountMin,
).
Where(
q.ProductSku.ProductID.In(pids...),
q.ProductSku.Status.Eq(int32(m.SkuStatusEnabled)),
).
Order(q.ProductSku.Sort).
Find()
if err != nil {
return nil, err
}
pmap := make(map[int32]*m.Product, len(products))
for _, p := range products {
pmap[p.ID] = p
p.Skus = make([]*m.ProductSku, 0)
}
for _, s := range skus {
if p, ok := pmap[s.ProductID]; ok {
p.Skus = append(p.Skus, s)
}
}
return products, nil
}
// 新增产品 // 新增产品
func (s *productService) CreateProduct(create *CreateProductData) error { func (s *productService) CreateProduct(create *CreateProductData) error {
return q.Product.Create(&m.Product{ return q.Product.Create(&m.Product{

View File

@@ -20,7 +20,7 @@ func (s *productSkuService) All(product_code string) (result []*m.ProductSku, er
Joins(q.ProductSku.Product). Joins(q.ProductSku.Product).
Where(q.Product.As("Product").Code.Eq(product_code)). Where(q.Product.As("Product").Code.Eq(product_code)).
Select(q.ProductSku.ALL). Select(q.ProductSku.ALL).
Order(q.ProductSku.CreatedAt.Desc()). Order(q.ProductSku.Sort).
Find() Find()
} }
@@ -30,9 +30,9 @@ func (s *productSkuService) Page(req *core.PageReq, productId *int32) (result []
do = append(do, q.ProductSku.ProductID.Eq(*productId)) do = append(do, q.ProductSku.ProductID.Eq(*productId))
} }
return q.ProductSku. return q.ProductSku.
Joins(q.ProductSku.Discount). Joins(q.ProductSku.Discount, q.ProductSku.Product).
Where(do...). Where(do...).
Order(q.ProductSku.CreatedAt.Desc()). Order(q.ProductSku.Sort).
FindByPage(req.GetOffset(), req.GetLimit()) FindByPage(req.GetOffset(), req.GetLimit())
} }
@@ -42,12 +42,25 @@ func (s *productSkuService) Create(create CreateProductSkuData) (err error) {
return core.NewBizErr("产品价格的格式不正确", err) return core.NewBizErr("产品价格的格式不正确", err)
} }
priceMin, err := decimal.NewFromString(create.PriceMin)
if err != nil {
return core.NewBizErr("产品最低价格的格式不正确", err)
}
countMin := int32(1)
if create.CountMin != nil {
countMin = *create.CountMin
}
return q.ProductSku.Create(&m.ProductSku{ return q.ProductSku.Create(&m.ProductSku{
ProductID: create.ProductID, ProductID: create.ProductID,
DiscountId: create.DiscountID, DiscountId: create.DiscountID,
Code: create.Code, Code: create.Code,
Name: create.Name, Name: create.Name,
Price: price, Price: price,
PriceMin: priceMin,
Sort: create.Sort,
CountMin: countMin,
}) })
} }
@@ -57,6 +70,9 @@ type CreateProductSkuData struct {
Code string `json:"code"` Code string `json:"code"`
Name string `json:"name"` Name string `json:"name"`
Price string `json:"price"` Price string `json:"price"`
PriceMin string `json:"price_min"`
Sort int32 `json:"sort"`
CountMin *int32 `json:"count_min"`
} }
func (s *productSkuService) Update(update UpdateProductSkuData) (err error) { func (s *productSkuService) Update(update UpdateProductSkuData) (err error) {
@@ -69,6 +85,13 @@ func (s *productSkuService) Update(update UpdateProductSkuData) (err error) {
} }
do = append(do, q.ProductSku.Price.Value(price)) do = append(do, q.ProductSku.Price.Value(price))
} }
if update.PriceMin != "" {
priceMin, err := decimal.NewFromString(update.PriceMin)
if err != nil {
return core.NewBizErr("产品最低价格的格式不正确", err)
}
do = append(do, q.ProductSku.PriceMin.Value(priceMin))
}
if update.DiscountID != nil { if update.DiscountID != nil {
do = append(do, q.ProductSku.DiscountId.Value(*update.DiscountID)) do = append(do, q.ProductSku.DiscountId.Value(*update.DiscountID))
} }
@@ -78,6 +101,15 @@ func (s *productSkuService) Update(update UpdateProductSkuData) (err error) {
if update.Name != nil { if update.Name != nil {
do = append(do, q.ProductSku.Name.Value(*update.Name)) do = append(do, q.ProductSku.Name.Value(*update.Name))
} }
if update.Status != nil {
do = append(do, q.ProductSku.Status.Value(*update.Status))
}
if update.Sort != nil {
do = append(do, q.ProductSku.Sort.Value(*update.Sort))
}
if update.CountMin != nil {
do = append(do, q.ProductSku.CountMin.Value(*update.CountMin))
}
_, err = q.ProductSku.Where(q.ProductSku.ID.Eq(update.ID)).UpdateSimple(do...) _, err = q.ProductSku.Where(q.ProductSku.ID.Eq(update.ID)).UpdateSimple(do...)
return err return err
@@ -89,6 +121,10 @@ type UpdateProductSkuData struct {
Code *string `json:"code"` Code *string `json:"code"`
Name *string `json:"name"` Name *string `json:"name"`
Price *string `json:"price"` Price *string `json:"price"`
PriceMin string `json:"price_min"`
Status *int32 `json:"status"`
Sort *int32 `json:"sort"`
CountMin *int32 `json:"count_min"`
} }
func (s *productSkuService) Delete(id int32) (err error) { func (s *productSkuService) Delete(id int32) (err error) {

View File

@@ -1,65 +1,216 @@
package services package services
import ( import (
"context"
"fmt" "fmt"
"net/netip" "net/netip"
"platform/pkg/u" "platform/pkg/u"
"platform/web/core" "platform/web/core"
g "platform/web/globals"
"platform/web/globals/orm" "platform/web/globals/orm"
m "platform/web/models" m "platform/web/models"
q "platform/web/queries" q "platform/web/queries"
"strconv"
"time" "time"
"gorm.io/gen/field"
) )
var Proxy = &proxyService{} var Proxy = &proxyService{}
type proxyService struct{} type proxyService struct{}
// AllProxies 获取所有代理 func proxyStatusLockKey(id int32) string {
func (s *proxyService) AllProxies(proxyType m.ProxyType, channels bool) ([]*m.Proxy, error) { return fmt.Sprintf("platform:proxy:status:%d", id)
proxies, err := q.Proxy.Where(
q.Proxy.Type.Eq(int(proxyType)),
q.Proxy.Status.Eq(int(m.ProxyStatusOnline)),
).Preload(
q.Proxy.Channels.On(q.Channel.ExpiredAt.Gte(time.Now())),
).Find()
if err != nil {
return nil, err
}
return proxies, nil
} }
// RegisterBaiyin 注册新代理服务 func hasUsedChans(proxyID int32) (bool, error) {
func (s *proxyService) RegisterBaiyin(Name string, IP netip.Addr, username, password string) error { ctx := context.Background()
pattern := usedChansKey + ":" + strconv.Itoa(int(proxyID)) + ":*"
// 保存代理信息 keys, _, err := g.Redis.Scan(ctx, 0, pattern, 1).Result()
proxy := &m.Proxy{ if err != nil {
Version: 0, return false, err
Mac: Name,
IP: orm.Inet{Addr: IP},
Secret: u.P(fmt.Sprintf("%s:%s", username, password)),
Type: m.ProxyTypeBaiYin,
Status: m.ProxyStatusOnline,
} }
if err := q.Proxy.Create(proxy); err != nil { return len(keys) > 0, nil
return core.NewServErr("保存通道数据失败") }
func rebuildFreeChans(proxyID int32, addr netip.Addr) error {
if err := remChans(proxyID); err != nil {
return err
} }
// 添加可用通道到 redis
chans := make([]netip.AddrPort, 10000) chans := make([]netip.AddrPort, 10000)
for i := range 10000 { for i := range 10000 {
chans[i] = netip.AddrPortFrom(IP, uint16(i+10000)) chans[i] = netip.AddrPortFrom(addr, uint16(i+10000))
} }
err := regChans(proxy.ID, chans)
if err := regChans(proxyID, chans); err != nil {
return err
}
return nil
}
func (s *proxyService) Page(req core.PageReq) (result []*m.Proxy, count int64, err error) {
return q.Proxy.
Omit(q.Proxy.Version, q.Proxy.Meta).
Order(q.Proxy.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
}
func (s *proxyService) All() (result []*m.Proxy, err error) {
return q.Proxy.
Omit(q.Proxy.Version, q.Proxy.Meta).
Order(q.Proxy.CreatedAt.Desc()).
Find()
}
type CreateProxy struct {
Mac string `json:"mac" validate:"required"`
IP string `json:"ip" validate:"required"`
Host *string `json:"host"`
Secret *string `json:"secret"`
Type *m.ProxyType `json:"type"`
Status *m.ProxyStatus `json:"status"`
}
func (s *proxyService) Create(create *CreateProxy) error {
addr, err := netip.ParseAddr(create.IP)
if err != nil { if err != nil {
return core.NewServErr("添加通道失败", err) return core.NewServErr("IP地址格式错误", err)
} }
return q.Q.Transaction(func(tx *q.Query) error {
proxy := &m.Proxy{
Mac: create.Mac,
IP: orm.Inet{Addr: addr},
Host: create.Host,
Secret: create.Secret,
Type: u.Else(create.Type, m.ProxyTypeSelfHosted),
Status: u.Else(create.Status, m.ProxyStatusOffline),
}
if err := tx.Proxy.Create(proxy); err != nil {
return core.NewServErr("保存代理数据失败", err)
}
if err := rebuildFreeChans(proxy.ID, addr); err != nil {
return core.NewServErr("初始化代理通道失败", err)
}
return nil
})
}
type UpdateProxy struct {
ID int32 `json:"id" validate:"required"`
Mac *string `json:"mac"`
IP *string `json:"ip"`
Host *string `json:"host"`
Secret *string `json:"secret"`
}
func (s *proxyService) Update(update *UpdateProxy) error {
simples := make([]field.AssignExpr, 0)
hasSideEffect := false
if update.Mac != nil {
hasSideEffect = true
simples = append(simples, q.Proxy.Mac.Value(*update.Mac))
}
if update.IP != nil {
addr, err := netip.ParseAddr(*update.IP)
if err != nil {
return core.NewServErr("IP地址格式错误", err)
}
hasSideEffect = true
simples = append(simples, q.Proxy.IP.Value(orm.Inet{Addr: addr}))
}
if update.Host != nil {
simples = append(simples, q.Proxy.Host.Value(*update.Host))
}
if update.Secret != nil {
hasSideEffect = true
simples = append(simples, q.Proxy.Secret.Value(*update.Secret))
}
if len(simples) == 0 {
return nil
}
if hasSideEffect {
used, err := hasUsedChans(update.ID)
if err != nil {
return core.NewServErr("检查代理通道状态失败", err)
}
if used {
return core.NewBizErr("代理存在未关闭通道,禁止修改")
}
}
rs, err := q.Proxy.
Where(
q.Proxy.ID.Eq(update.ID),
q.Proxy.Status.Eq(int(m.ProxyStatusOffline)),
).
UpdateSimple(simples...)
if err != nil {
return err
}
if rs.RowsAffected == 0 {
return core.NewBizErr("代理未下线,禁止修改")
}
return nil return nil
} }
// UnregisterBaiyin 注销代理服务 func (s *proxyService) Remove(id int32) error {
func (s *proxyService) UnregisterBaiyin(id int) error { used, err := hasUsedChans(id)
if err != nil {
return core.NewServErr("检查代理通道状态失败", err)
}
if used {
return core.NewBizErr("代理存在未关闭通道,禁止删除")
}
rs, err := q.Proxy.
Where(
q.Proxy.ID.Eq(id),
q.Proxy.Status.Eq(int(m.ProxyStatusOffline)),
).
UpdateColumn(q.Proxy.DeletedAt, time.Now())
if err != nil {
return err
}
if rs.RowsAffected == 0 {
return core.NewBizErr("代理未下线,禁止删除")
}
if err := remChans(id); err != nil {
return core.NewServErr("注销代理通道失败", err)
}
return nil return nil
} }
type UpdateProxyStatus struct {
ID int32 `json:"id" validate:"required"`
Status m.ProxyStatus `json:"status" validate:"required"`
}
func (s *proxyService) UpdateStatus(update *UpdateProxyStatus) error {
return g.Redsync.WithLock(proxyStatusLockKey(update.ID), func() error {
proxy, err := q.Proxy.Where(q.Proxy.ID.Eq(update.ID)).Take()
if err != nil {
return err
}
if proxy.Status == update.Status {
return nil
}
if update.Status == m.ProxyStatusOnline {
if err := rebuildFreeChans(proxy.ID, proxy.IP.Addr); err != nil {
return core.NewServErr("初始化代理通道失败", err)
}
}
_, err = q.Proxy.
Where(q.Proxy.ID.Eq(update.ID)).
UpdateSimple(q.Proxy.Status.Value(int(update.Status)))
return err
})
}

View File

@@ -2,6 +2,7 @@ package services
import ( import (
"fmt" "fmt"
"log/slog"
"platform/pkg/u" "platform/pkg/u"
"platform/web/core" "platform/web/core"
m "platform/web/models" m "platform/web/models"
@@ -28,11 +29,6 @@ func (s *resourceService) CreateResourceByBalance(user *m.User, data *CreateReso
return q.Q.Transaction(func(q *q.Query) error { return q.Q.Transaction(func(q *q.Query) error {
// 更新用户余额
if err := User.UpdateBalance(q, user, detail.Actual.Neg(), "余额购买产品", nil); err != nil {
return core.NewServErr("更新用户余额失败", err)
}
// 保存套餐 // 保存套餐
resource, err := s.Create(q, user.ID, now, data) resource, err := s.Create(q, user.ID, now, data)
if err != nil { if err != nil {
@@ -40,14 +36,19 @@ func (s *resourceService) CreateResourceByBalance(user *m.User, data *CreateReso
} }
// 生成账单 // 生成账单
err = Bill.CreateForResource(q, user.ID, resource.ID, nil, detail) bill, err := Bill.CreateForResource(q, user.ID, resource.ID, nil, detail)
if err != nil { if err != nil {
return core.NewServErr("生成账单失败", err) return core.NewServErr("生成账单失败", err)
} }
// 更新用户余额
if err := User.UpdateBalance(q, user, detail.Actual.Neg(), "余额购买产品", nil, &bill.ID); err != nil {
return core.NewServErr("更新用户余额失败", err)
}
// 核销优惠券 // 核销优惠券
if detail.CouponId != nil { if detail.CouponUserId != nil {
err = Coupon.UseCoupon(q, *detail.CouponId) err = Coupon.UseCoupon(q, *detail.CouponUserId)
if err != nil { if err != nil {
return core.NewServErr("核销优惠券失败", err) return core.NewServErr("核销优惠券失败", err)
} }
@@ -144,35 +145,36 @@ type UpdateResourceData struct {
Active *bool `json:"active"` Active *bool `json:"active"`
} }
func (s *resourceService) CalcPrice(skuCode string, count int32, user *m.User, couponCode *string) (*m.ProductSku, *m.ProductDiscount, *m.Coupon, decimal.Decimal, decimal.Decimal, error) { func (s *resourceService) CalcPrice(skuCode string, count int32, user *m.User, cuid *int32) (*m.ProductSku, *m.ProductDiscount, *m.CouponUser, decimal.Decimal, decimal.Decimal, decimal.Decimal, error) {
sku, err := q.ProductSku. sku, err := q.ProductSku.
Joins(q.ProductSku.Discount). Joins(q.ProductSku.Discount).
Where(q.ProductSku.Code.Eq(skuCode)). Where(q.ProductSku.Code.Eq(skuCode), q.ProductSku.Status.Eq(int32(m.SkuStatusEnabled))).
Take() Take()
if err != nil { if err != nil {
return nil, nil, nil, decimal.Zero, decimal.Zero, core.NewServErr("产品不可用", err) slog.Debug("查询产品失败", "skuCode", skuCode)
return nil, nil, nil, decimal.Zero, decimal.Zero, decimal.Zero, core.NewBizErr(fmt.Sprintf("产品不可用", skuCode), err)
} }
// 原价 // 原价
price := sku.Price amountMin := sku.PriceMin.Mul(decimal.NewFromInt32(count))
amount := price.Mul(decimal.NewFromInt32(count)) amount := sku.Price.Mul(decimal.NewFromInt32(count))
// 折扣价 // 折扣价
discount := sku.Discount discount := sku.Discount
if discount == nil { if discount == nil {
return nil, nil, nil, decimal.Zero, decimal.Zero, core.NewServErr("价格查询失败", err) return nil, nil, nil, decimal.Zero, decimal.Zero, decimal.Zero, core.NewServErr("价格查询失败", err)
} }
discountRate := discount.Rate() discountRate := discount.Rate()
if user != nil && user.DiscountID != nil { // 用户特殊优惠 if user != nil && user.DiscountID != nil { // 用户特殊优惠
uDiscount, err := q.ProductDiscount.Where(q.ProductDiscount.ID.Eq(*user.DiscountID)).Take() uDiscount, err := q.ProductDiscount.Where(q.ProductDiscount.ID.Eq(*user.DiscountID)).Take()
if err != nil { if err != nil {
return nil, nil, nil, decimal.Zero, decimal.Zero, core.NewServErr("客户特殊价查询失败", err) return nil, nil, nil, decimal.Zero, decimal.Zero, decimal.Zero, core.NewServErr("客户特殊价查询失败", err)
} }
uDiscountRate := uDiscount.Rate() uDiscountRate := uDiscount.Rate()
if uDiscountRate.Cmp(discountRate) > 0 { if uDiscountRate.Cmp(discountRate) < 0 {
discountRate = uDiscountRate discountRate = uDiscountRate
discount = uDiscount discount = uDiscount
} }
@@ -180,30 +182,33 @@ func (s *resourceService) CalcPrice(skuCode string, count int32, user *m.User, c
discounted := amount.Mul(discountRate) discounted := amount.Mul(discountRate)
// 优惠价 // 优惠价
uid := (*int32)(nil) coupon := (*m.CouponUser)(nil)
if user != nil {
uid = &user.ID
}
coupon := (*m.Coupon)(nil)
couponApplied := discounted.Copy() couponApplied := discounted.Copy()
if couponCode != nil { if user != nil && cuid != nil {
var err error var err error
coupon, err = Coupon.GetCouponAvailableByCode(*couponCode, discounted, uid) coupon, err = Coupon.GetUserCoupon(user.ID, *cuid, discounted)
if err != nil { if err != nil {
return nil, nil, nil, decimal.Zero, decimal.Zero, err return nil, nil, nil, decimal.Zero, decimal.Zero, decimal.Zero, err
} }
couponApplied = discounted.Sub(coupon.Amount) couponApplied = discounted.Sub(coupon.Coupon.Amount)
} }
return sku, discount, coupon, discounted, couponApplied, nil // 约束到最低价格
if discounted.Cmp(amountMin) < 0 {
discounted = amountMin.Copy()
}
if couponApplied.Cmp(amountMin) < 0 {
couponApplied = amountMin.Copy()
}
return sku, discount, coupon, amount, discounted, couponApplied, nil
} }
type CreateResourceData struct { type CreateResourceData struct {
Type m.ResourceType `json:"type" validate:"required"` Type m.ResourceType `json:"type" validate:"required"`
Short *CreateShortResourceData `json:"short,omitempty"` Short *CreateShortResourceData `json:"short,omitempty"`
Long *CreateLongResourceData `json:"long,omitempty"` Long *CreateLongResourceData `json:"long,omitempty"`
CouponCode *string `json:"coupon,omitempty"` CouponId *int32 `json:"coupon,omitempty"`
} }
type CreateShortResourceData struct { type CreateShortResourceData struct {
@@ -244,20 +249,20 @@ func (data *CreateResourceData) Code() string {
case data.Type == m.ResourceTypeShort && data.Short != nil: case data.Type == m.ResourceTypeShort && data.Short != nil:
return fmt.Sprintf( return fmt.Sprintf(
"mode=%s,live=%d,expire=%d", "mode=%s&live=%d&expire=%d",
data.Short.Mode.Code(), data.Short.Live, u.Else(data.Short.Expire, 0), data.Short.Mode.Code(), data.Short.Live, u.Else(data.Short.Expire, 0),
) )
case data.Type == m.ResourceTypeLong && data.Long != nil: case data.Type == m.ResourceTypeLong && data.Long != nil:
return fmt.Sprintf( return fmt.Sprintf(
"mode=%s,live=%d,expire=%d", "mode=%s&live=%d&expire=%d",
data.Long.Mode.Code(), data.Long.Live, u.Else(data.Long.Expire, 0), data.Long.Mode.Code(), data.Long.Live, u.Else(data.Long.Expire, 0),
) )
} }
} }
func (data *CreateResourceData) TradeDetail(user *m.User) (*TradeDetail, error) { func (data *CreateResourceData) TradeDetail(user *m.User) (*TradeDetail, error) {
sku, discount, coupon, amount, actual, err := Resource.CalcPrice(data.Code(), data.Count(), user, data.CouponCode) sku, discount, coupon, amount, discounted, actual, err := Resource.CalcPrice(data.Code(), data.Count(), user, data.CouponId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -266,17 +271,16 @@ func (data *CreateResourceData) TradeDetail(user *m.User) (*TradeDetail, error)
if discount != nil { if discount != nil {
discountId = &discount.ID discountId = &discount.ID
} }
var couponId *int32 = nil var couponUserId *int32 = nil
if coupon != nil { if coupon != nil {
couponId = &coupon.ID couponUserId = &coupon.ID
} }
return &TradeDetail{ return &TradeDetail{
data, data,
m.TradeTypePurchase, m.TradeTypePurchase,
sku.Name, sku.Name,
amount, actual, amount, discounted, actual,
discountId, discount, discountId, couponUserId,
couponId, coupon,
}, nil }, nil
} }

View File

@@ -302,17 +302,18 @@ func (s *tradeService) OnCompleteTrade(user *m.User, interNo string, outerNo str
switch trade.Type { switch trade.Type {
case m.TradeTypeRecharge: case m.TradeTypeRecharge:
// 更新用户余额
if err := User.UpdateBalance(q, user, detail.Actual, "充值余额", nil); err != nil {
return err
}
// 生成账单 // 生成账单
err = Bill.CreateForBalance(q, user.ID, trade.ID, &detail) bill, err := Bill.CreateForBalance(q, user.ID, trade.ID, &detail)
if err != nil { if err != nil {
return core.NewServErr("生成账单失败", err) return core.NewServErr("生成账单失败", err)
} }
// 更新用户余额
if err := User.UpdateBalance(q, user, detail.Actual, "充值余额", nil, &bill.ID); err != nil {
return err
}
case m.TradeTypePurchase: case m.TradeTypePurchase:
data, ok := detail.Product.(*CreateResourceData) data, ok := detail.Product.(*CreateResourceData)
if !ok { if !ok {
@@ -326,14 +327,14 @@ func (s *tradeService) OnCompleteTrade(user *m.User, interNo string, outerNo str
} }
// 生成账单 // 生成账单
err = Bill.CreateForResource(q, user.ID, resource.ID, &trade.ID, &detail) _, err = Bill.CreateForResource(q, user.ID, resource.ID, &trade.ID, &detail)
if err != nil { if err != nil {
return core.NewServErr("生成账单失败", err) return core.NewServErr("生成账单失败", err)
} }
// 核销优惠券 // 核销优惠券
if detail.CouponId != nil { if detail.CouponUserId != nil {
err = Coupon.UseCoupon(q, *detail.CouponId) err = Coupon.UseCoupon(q, *detail.CouponUserId)
if err != nil { if err != nil {
return core.NewServErr("核销优惠券失败", err) return core.NewServErr("核销优惠券失败", err)
} }
@@ -618,11 +619,10 @@ type TradeDetail struct {
Type m.TradeType `json:"type"` Type m.TradeType `json:"type"`
Subject string `json:"subject"` Subject string `json:"subject"`
Amount decimal.Decimal `json:"amount"` Amount decimal.Decimal `json:"amount"`
Discounted decimal.Decimal `json:"discounted"`
Actual decimal.Decimal `json:"actual"` Actual decimal.Decimal `json:"actual"`
DiscountId *int32 `json:"discount_id,omitempty"` DiscountId *int32 `json:"discount_id,omitempty"`
Discount *m.ProductDiscount `json:"-"` // 不应缓存 CouponUserId *int32 `json:"coupon_id,omitempty"`
CouponId *int32 `json:"coupon_id,omitempty"`
Coupon *m.Coupon `json:"-"` // 不应缓存
} }
type TradeErr string type TradeErr string

View File

@@ -39,11 +39,11 @@ func (s *userService) UpdateBalanceByAdmin(user *m.User, newBalance decimal.Deci
} }
return q.Q.Transaction(func(q *q.Query) error { return q.Q.Transaction(func(q *q.Query) error {
return s.UpdateBalance(q, user, amount, "管理员修改余额", adminId) return s.UpdateBalance(q, user, amount, "管理员修改余额", adminId, nil)
}) })
} }
func (s *userService) UpdateBalance(q *q.Query, user *m.User, amount decimal.Decimal, remark string, adminId *int32) error { func (s *userService) UpdateBalance(q *q.Query, user *m.User, amount decimal.Decimal, remark string, adminId *int32, billId *int32) error {
balance := user.Balance.Add(amount) balance := user.Balance.Add(amount)
if balance.IsNegative() { if balance.IsNegative() {
return core.NewServErr("用户余额不足") return core.NewServErr("用户余额不足")
@@ -66,6 +66,7 @@ func (s *userService) UpdateBalance(q *q.Query, user *m.User, amount decimal.Dec
err = q.BalanceActivity.Create(&m.BalanceActivity{ err = q.BalanceActivity.Create(&m.BalanceActivity{
UserID: user.ID, UserID: user.ID,
AdminID: adminId, AdminID: adminId,
BillID: billId,
Amount: amount.StringFixed(2), Amount: amount.StringFixed(2),
BalancePrev: user.Balance.StringFixed(2), BalancePrev: user.Balance.StringFixed(2),
BalanceCurr: balance.StringFixed(2), BalanceCurr: balance.StringFixed(2),
@@ -92,8 +93,7 @@ func (data *UpdateBalanceData) TradeDetail(user *m.User) (*TradeDetail, error) {
data, data,
m.TradeTypeRecharge, m.TradeTypeRecharge,
fmt.Sprintf("账户充值 - %s元", amount.StringFixed(2)), fmt.Sprintf("账户充值 - %s元", amount.StringFixed(2)),
amount, amount, amount, amount, amount,
nil, nil,
nil, nil, nil, nil,
}, nil }, nil
} }
@@ -118,7 +118,6 @@ func (s *userService) CreateByAdmin(data CreateUserByAdminData) error {
Email: data.Email, Email: data.Email,
Password: hashedPwd, Password: hashedPwd,
Source: &source, Source: &source,
Name: data.Name,
Avatar: data.Avatar, Avatar: data.Avatar,
Status: u.Else(data.Status, m.UserStatusEnabled), Status: u.Else(data.Status, m.UserStatusEnabled),
ContactQQ: data.ContactQQ, ContactQQ: data.ContactQQ,
@@ -141,7 +140,6 @@ type CreateUserByAdminData struct {
Username *string `json:"username"` Username *string `json:"username"`
Email *string `json:"email"` Email *string `json:"email"`
Password *string `json:"password"` Password *string `json:"password"`
Name *string `json:"name"`
Avatar *string `json:"avatar"` Avatar *string `json:"avatar"`
Status *m.UserStatus `json:"status"` Status *m.UserStatus `json:"status"`
ContactQQ *string `json:"contact_qq"` ContactQQ *string `json:"contact_qq"`

View File

@@ -55,7 +55,7 @@ func (s *verifierService) SendSms(ctx context.Context, phone string, purpose Ver
code := rand.Intn(900000) + 100000 // 6-digit code between 100000-999999 code := rand.Intn(900000) + 100000 // 6-digit code between 100000-999999
// 发送短信验证码 // 发送短信验证码
if env.DebugExternalChange { if env.RunMode == env.RunModeProd {
params, err := json.Marshal(map[string]string{ params, err := json.Marshal(map[string]string{
"code": strconv.Itoa(code), "code": strconv.Itoa(code),
}) })
@@ -89,8 +89,8 @@ func (s *verifierService) SendSms(ctx context.Context, phone string, purpose Ver
return nil return nil
} }
func (s *verifierService) VerifySms(ctx context.Context, phone, code string) error { func (s *verifierService) VerifySms(ctx context.Context, phone, code string, purpose VerifierSmsPurpose) error {
key := smsKey(phone, VerifierSmsPurposeLogin) key := smsKey(phone, purpose)
keyLock := key + ":lock" keyLock := key + ":lock"
err := g.Redis.Watch(ctx, func(tx *redis.Tx) error { err := g.Redis.Watch(ctx, func(tx *redis.Tx) error {
@@ -147,6 +147,7 @@ type VerifierSmsPurpose int
const ( const (
VerifierSmsPurposeLogin VerifierSmsPurpose = iota // 登录 VerifierSmsPurposeLogin VerifierSmsPurpose = iota // 登录
VerifierSmsPurposePassword // 修改密码
) )
// region 服务异常 // region 服务异常