实现用户咨询数据收集接口
This commit is contained in:
16
.vscode/launch.json
vendored
16
.vscode/launch.json
vendored
@@ -1,16 +0,0 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "main",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/cmd/main",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
.zed/debug.json
Normal file
13
.zed/debug.json
Normal file
@@ -0,0 +1,13 @@
|
||||
// Project-local debug tasks
|
||||
//
|
||||
// For more documentation on how to configure debug tasks,
|
||||
// see: https://zed.dev/docs/debugger
|
||||
[
|
||||
{
|
||||
"label": "debug main",
|
||||
"adapter": "Delve",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "./cmd/main"
|
||||
}
|
||||
]
|
||||
18
README.md
18
README.md
@@ -1,19 +1,15 @@
|
||||
## TODO
|
||||
|
||||
价格与优惠
|
||||
|
||||
优化中间件,配置通用限速
|
||||
|
||||
trade/create 性能问题,缩短事务时间,考虑其他方式实现可靠分布式事务
|
||||
|
||||
jsonb 类型转换问题,考虑一个高效的 any 到 struct 转换工具
|
||||
|
||||
端口资源池的 gc 实现
|
||||
|
||||
channel 服务代码结构,用 provider 代替整个 service 的复用
|
||||
|
||||
用反射实现环境变量解析,以简化函数签名
|
||||
observe 部署,蓝狐部署
|
||||
|
||||
---
|
||||
|
||||
用反射实现环境变量解析,以简化函数签名
|
||||
|
||||
分离 task 的客户端,支持多进程(prefork 必要!)
|
||||
|
||||
调整目录结构:
|
||||
@@ -39,6 +35,10 @@ http 调用 clients 的初始化函数
|
||||
|
||||
---
|
||||
|
||||
数据库转模型文件
|
||||
|
||||
jsonb 类型转换问题,考虑一个高效的 any 到 struct 转换工具
|
||||
|
||||
慢速请求底层调用埋点监控
|
||||
- redis
|
||||
- gorm
|
||||
|
||||
@@ -61,6 +61,7 @@ func main() {
|
||||
m.User{},
|
||||
m.UserRole{},
|
||||
m.Whitelist{},
|
||||
m.Inquiry{},
|
||||
)
|
||||
g.Execute()
|
||||
}
|
||||
|
||||
@@ -108,6 +108,76 @@ comment on column logs_user_bandwidth.time is '记录时间';
|
||||
|
||||
-- endregion
|
||||
|
||||
-- ====================
|
||||
-- region 系统信息
|
||||
-- ====================
|
||||
|
||||
-- announcement
|
||||
drop table if exists announcement cascade;
|
||||
create table announcement (
|
||||
id int generated by default as identity primary key,
|
||||
title text not null,
|
||||
content text,
|
||||
type int not null default 1,
|
||||
pin bool not null default false,
|
||||
status int not null default 1,
|
||||
sort int not null default 0,
|
||||
created_at timestamptz default current_timestamp,
|
||||
updated_at timestamptz default current_timestamp,
|
||||
deleted_at timestamptz
|
||||
);
|
||||
create index idx_announcement_type on announcement (type) where deleted_at is null;
|
||||
create index idx_announcement_pin on announcement (pin) where deleted_at is null;
|
||||
create index idx_announcement_created_at on announcement (created_at) where deleted_at is null;
|
||||
|
||||
-- announcement表字段注释
|
||||
comment on table announcement is '公告表';
|
||||
comment on column announcement.id is '公告ID';
|
||||
comment on column announcement.title is '公告标题';
|
||||
comment on column announcement.content is '公告内容';
|
||||
comment on column announcement.type is '公告类型:1-普通公告';
|
||||
comment on column announcement.status is '公告状态:0-禁用,1-正常';
|
||||
comment on column announcement.pin is '是否置顶';
|
||||
comment on column announcement.sort is '公告排序';
|
||||
comment on column announcement.created_at is '创建时间';
|
||||
comment on column announcement.updated_at is '更新时间';
|
||||
comment on column announcement.deleted_at is '删除时间';
|
||||
|
||||
-- inquiry
|
||||
drop table if exists inquiry cascade;
|
||||
create table inquiry (
|
||||
id int generated by default as identity primary key,
|
||||
company text,
|
||||
name text,
|
||||
phone text,
|
||||
email text,
|
||||
content text,
|
||||
status int not null default 0,
|
||||
remark text,
|
||||
created_at timestamptz default current_timestamp,
|
||||
updated_at timestamptz default current_timestamp,
|
||||
deleted_at timestamptz
|
||||
);
|
||||
create index idx_inquiry_phone on inquiry (phone) where deleted_at is null;
|
||||
create index idx_inquiry_status on inquiry (status) where deleted_at is null;
|
||||
create index idx_inquiry_created_at on inquiry (created_at) where deleted_at is null;
|
||||
|
||||
-- inquiry表字段注释
|
||||
comment on table inquiry is '用户咨询表';
|
||||
comment on column inquiry.id is '咨询ID';
|
||||
comment on column inquiry.name is '联系人姓名';
|
||||
comment on column inquiry.phone is '联系电话';
|
||||
comment on column inquiry.email is '联系邮箱';
|
||||
comment on column inquiry.company is '公司名称';
|
||||
comment on column inquiry.content is '咨询内容';
|
||||
comment on column inquiry.status is '处理状态:0-待处理,1-已处理';
|
||||
comment on column inquiry.remark is '备注';
|
||||
comment on column inquiry.created_at is '创建时间';
|
||||
comment on column inquiry.updated_at is '更新时间';
|
||||
comment on column inquiry.deleted_at is '删除时间';
|
||||
|
||||
-- endregion
|
||||
|
||||
-- ====================
|
||||
-- region 管理员信息
|
||||
-- ====================
|
||||
@@ -177,37 +247,6 @@ comment on column admin_role.created_at is '创建时间';
|
||||
comment on column admin_role.updated_at is '更新时间';
|
||||
comment on column admin_role.deleted_at is '删除时间';
|
||||
|
||||
-- announcement
|
||||
drop table if exists announcement cascade;
|
||||
create table announcement (
|
||||
id int generated by default as identity primary key,
|
||||
title text not null,
|
||||
content text,
|
||||
type int not null default 1,
|
||||
pin bool not null default false,
|
||||
status int not null default 1,
|
||||
sort int not null default 0,
|
||||
created_at timestamptz default current_timestamp,
|
||||
updated_at timestamptz default current_timestamp,
|
||||
deleted_at timestamptz
|
||||
);
|
||||
create index idx_announcement_type on announcement (type) where deleted_at is null;
|
||||
create index idx_announcement_pin on announcement (pin) where deleted_at is null;
|
||||
create index idx_announcement_created_at on announcement (created_at) where deleted_at is null;
|
||||
|
||||
-- announcement表字段注释
|
||||
comment on table announcement is '公告表';
|
||||
comment on column announcement.id is '公告ID';
|
||||
comment on column announcement.title is '公告标题';
|
||||
comment on column announcement.content is '公告内容';
|
||||
comment on column announcement.type is '公告类型:1-普通公告';
|
||||
comment on column announcement.status is '公告状态:0-禁用,1-正常';
|
||||
comment on column announcement.pin is '是否置顶';
|
||||
comment on column announcement.sort is '公告排序';
|
||||
comment on column announcement.created_at is '创建时间';
|
||||
comment on column announcement.updated_at is '更新时间';
|
||||
comment on column announcement.deleted_at is '删除时间';
|
||||
|
||||
-- endregion
|
||||
|
||||
-- ====================
|
||||
|
||||
48
web/handlers/inquiry.go
Normal file
48
web/handlers/inquiry.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
g "platform/web/globals"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// region CreateInquiry
|
||||
|
||||
type CreateInquiryRequest struct {
|
||||
Company string `json:"company" validate:"omitempty,max=200"`
|
||||
Name string `json:"name" validate:"required,max=100"`
|
||||
Phone string `json:"phone" validate:"required,max=20"`
|
||||
Email string `json:"email" validate:"omitempty,email,max=100"`
|
||||
Content string `json:"content" validate:"required,max=1000"`
|
||||
}
|
||||
|
||||
func CreateInquiry(c *fiber.Ctx) error {
|
||||
|
||||
// 解析请求参数
|
||||
req := new(CreateInquiryRequest)
|
||||
err := g.Validator.ParseBody(c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建咨询记录
|
||||
err = q.Inquiry.Create(&m.Inquiry{
|
||||
Company: u.X(req.Company),
|
||||
Name: u.X(req.Name),
|
||||
Phone: u.X(req.Phone),
|
||||
Email: u.X(req.Email),
|
||||
Content: u.X(req.Content),
|
||||
Status: m.InquiryStatusPending,
|
||||
})
|
||||
if err != nil {
|
||||
return core.NewServErr("提交咨询失败", err)
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
// endregion
|
||||
@@ -439,7 +439,7 @@ func ResourcePrice(c *fiber.Ctx) error {
|
||||
// 解析请求参数
|
||||
var req = new(CreateResourceReq)
|
||||
if err := g.Validator.ParseBody(c, req); err != nil {
|
||||
return err
|
||||
return core.NewBizErr("接口参数解析异常", err)
|
||||
}
|
||||
|
||||
// 获取套餐价格
|
||||
|
||||
25
web/models/inquiry.go
Normal file
25
web/models/inquiry.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"platform/web/core"
|
||||
)
|
||||
|
||||
// Inquiry 用户咨询表
|
||||
type Inquiry struct {
|
||||
core.Model
|
||||
Company *string `json:"company,omitempty" gorm:"column:company"` // 公司名称
|
||||
Name *string `json:"name,omitempty" gorm:"column:name"` // 联系人姓名
|
||||
Phone *string `json:"phone,omitempty" gorm:"column:phone"` // 联系电话
|
||||
Email *string `json:"email,omitempty" gorm:"column:email"` // 联系邮箱
|
||||
Content *string `json:"content,omitempty" gorm:"column:content"` // 咨询内容
|
||||
Status InquiryStatus `json:"status" gorm:"column:status"` // 处理状态:0-待处理,1-已处理
|
||||
Remark *string `json:"remark,omitempty" gorm:"column:remark"` // 备注
|
||||
}
|
||||
|
||||
// InquiryStatus 咨询处理状态枚举
|
||||
type InquiryStatus int
|
||||
|
||||
const (
|
||||
InquiryStatusPending InquiryStatus = 0 // 待处理
|
||||
InquiryStatusProcessed InquiryStatus = 1 // 已处理
|
||||
)
|
||||
@@ -25,6 +25,7 @@ var (
|
||||
Client *client
|
||||
Coupon *coupon
|
||||
Edge *edge
|
||||
Inquiry *inquiry
|
||||
LinkAdminRole *linkAdminRole
|
||||
LinkAdminRolePermission *linkAdminRolePermission
|
||||
LinkClientPermission *linkClientPermission
|
||||
@@ -58,6 +59,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
||||
Client = &Q.Client
|
||||
Coupon = &Q.Coupon
|
||||
Edge = &Q.Edge
|
||||
Inquiry = &Q.Inquiry
|
||||
LinkAdminRole = &Q.LinkAdminRole
|
||||
LinkAdminRolePermission = &Q.LinkAdminRolePermission
|
||||
LinkClientPermission = &Q.LinkClientPermission
|
||||
@@ -92,6 +94,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
||||
Client: newClient(db, opts...),
|
||||
Coupon: newCoupon(db, opts...),
|
||||
Edge: newEdge(db, opts...),
|
||||
Inquiry: newInquiry(db, opts...),
|
||||
LinkAdminRole: newLinkAdminRole(db, opts...),
|
||||
LinkAdminRolePermission: newLinkAdminRolePermission(db, opts...),
|
||||
LinkClientPermission: newLinkClientPermission(db, opts...),
|
||||
@@ -127,6 +130,7 @@ type Query struct {
|
||||
Client client
|
||||
Coupon coupon
|
||||
Edge edge
|
||||
Inquiry inquiry
|
||||
LinkAdminRole linkAdminRole
|
||||
LinkAdminRolePermission linkAdminRolePermission
|
||||
LinkClientPermission linkClientPermission
|
||||
@@ -163,6 +167,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
|
||||
Client: q.Client.clone(db),
|
||||
Coupon: q.Coupon.clone(db),
|
||||
Edge: q.Edge.clone(db),
|
||||
Inquiry: q.Inquiry.clone(db),
|
||||
LinkAdminRole: q.LinkAdminRole.clone(db),
|
||||
LinkAdminRolePermission: q.LinkAdminRolePermission.clone(db),
|
||||
LinkClientPermission: q.LinkClientPermission.clone(db),
|
||||
@@ -206,6 +211,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
||||
Client: q.Client.replaceDB(db),
|
||||
Coupon: q.Coupon.replaceDB(db),
|
||||
Edge: q.Edge.replaceDB(db),
|
||||
Inquiry: q.Inquiry.replaceDB(db),
|
||||
LinkAdminRole: q.LinkAdminRole.replaceDB(db),
|
||||
LinkAdminRolePermission: q.LinkAdminRolePermission.replaceDB(db),
|
||||
LinkClientPermission: q.LinkClientPermission.replaceDB(db),
|
||||
@@ -239,6 +245,7 @@ type queryCtx struct {
|
||||
Client *clientDo
|
||||
Coupon *couponDo
|
||||
Edge *edgeDo
|
||||
Inquiry *inquiryDo
|
||||
LinkAdminRole *linkAdminRoleDo
|
||||
LinkAdminRolePermission *linkAdminRolePermissionDo
|
||||
LinkClientPermission *linkClientPermissionDo
|
||||
@@ -272,6 +279,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
||||
Client: q.Client.WithContext(ctx),
|
||||
Coupon: q.Coupon.WithContext(ctx),
|
||||
Edge: q.Edge.WithContext(ctx),
|
||||
Inquiry: q.Inquiry.WithContext(ctx),
|
||||
LinkAdminRole: q.LinkAdminRole.WithContext(ctx),
|
||||
LinkAdminRolePermission: q.LinkAdminRolePermission.WithContext(ctx),
|
||||
LinkClientPermission: q.LinkClientPermission.WithContext(ctx),
|
||||
|
||||
359
web/queries/inquiry.gen.go
Normal file
359
web/queries/inquiry.gen.go
Normal file
@@ -0,0 +1,359 @@
|
||||
// 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 newInquiry(db *gorm.DB, opts ...gen.DOOption) inquiry {
|
||||
_inquiry := inquiry{}
|
||||
|
||||
_inquiry.inquiryDo.UseDB(db, opts...)
|
||||
_inquiry.inquiryDo.UseModel(&models.Inquiry{})
|
||||
|
||||
tableName := _inquiry.inquiryDo.TableName()
|
||||
_inquiry.ALL = field.NewAsterisk(tableName)
|
||||
_inquiry.ID = field.NewInt32(tableName, "id")
|
||||
_inquiry.CreatedAt = field.NewTime(tableName, "created_at")
|
||||
_inquiry.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||
_inquiry.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||
_inquiry.Company = field.NewString(tableName, "company")
|
||||
_inquiry.Name = field.NewString(tableName, "name")
|
||||
_inquiry.Phone = field.NewString(tableName, "phone")
|
||||
_inquiry.Email = field.NewString(tableName, "email")
|
||||
_inquiry.Content = field.NewString(tableName, "content")
|
||||
_inquiry.Status = field.NewInt(tableName, "status")
|
||||
_inquiry.Remark = field.NewString(tableName, "remark")
|
||||
|
||||
_inquiry.fillFieldMap()
|
||||
|
||||
return _inquiry
|
||||
}
|
||||
|
||||
type inquiry struct {
|
||||
inquiryDo
|
||||
|
||||
ALL field.Asterisk
|
||||
ID field.Int32
|
||||
CreatedAt field.Time
|
||||
UpdatedAt field.Time
|
||||
DeletedAt field.Field
|
||||
Company field.String
|
||||
Name field.String
|
||||
Phone field.String
|
||||
Email field.String
|
||||
Content field.String
|
||||
Status field.Int
|
||||
Remark field.String
|
||||
|
||||
fieldMap map[string]field.Expr
|
||||
}
|
||||
|
||||
func (i inquiry) Table(newTableName string) *inquiry {
|
||||
i.inquiryDo.UseTable(newTableName)
|
||||
return i.updateTableName(newTableName)
|
||||
}
|
||||
|
||||
func (i inquiry) As(alias string) *inquiry {
|
||||
i.inquiryDo.DO = *(i.inquiryDo.As(alias).(*gen.DO))
|
||||
return i.updateTableName(alias)
|
||||
}
|
||||
|
||||
func (i *inquiry) updateTableName(table string) *inquiry {
|
||||
i.ALL = field.NewAsterisk(table)
|
||||
i.ID = field.NewInt32(table, "id")
|
||||
i.CreatedAt = field.NewTime(table, "created_at")
|
||||
i.UpdatedAt = field.NewTime(table, "updated_at")
|
||||
i.DeletedAt = field.NewField(table, "deleted_at")
|
||||
i.Company = field.NewString(table, "company")
|
||||
i.Name = field.NewString(table, "name")
|
||||
i.Phone = field.NewString(table, "phone")
|
||||
i.Email = field.NewString(table, "email")
|
||||
i.Content = field.NewString(table, "content")
|
||||
i.Status = field.NewInt(table, "status")
|
||||
i.Remark = field.NewString(table, "remark")
|
||||
|
||||
i.fillFieldMap()
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *inquiry) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||
_f, ok := i.fieldMap[fieldName]
|
||||
if !ok || _f == nil {
|
||||
return nil, false
|
||||
}
|
||||
_oe, ok := _f.(field.OrderExpr)
|
||||
return _oe, ok
|
||||
}
|
||||
|
||||
func (i *inquiry) fillFieldMap() {
|
||||
i.fieldMap = make(map[string]field.Expr, 11)
|
||||
i.fieldMap["id"] = i.ID
|
||||
i.fieldMap["created_at"] = i.CreatedAt
|
||||
i.fieldMap["updated_at"] = i.UpdatedAt
|
||||
i.fieldMap["deleted_at"] = i.DeletedAt
|
||||
i.fieldMap["company"] = i.Company
|
||||
i.fieldMap["name"] = i.Name
|
||||
i.fieldMap["phone"] = i.Phone
|
||||
i.fieldMap["email"] = i.Email
|
||||
i.fieldMap["content"] = i.Content
|
||||
i.fieldMap["status"] = i.Status
|
||||
i.fieldMap["remark"] = i.Remark
|
||||
}
|
||||
|
||||
func (i inquiry) clone(db *gorm.DB) inquiry {
|
||||
i.inquiryDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||
return i
|
||||
}
|
||||
|
||||
func (i inquiry) replaceDB(db *gorm.DB) inquiry {
|
||||
i.inquiryDo.ReplaceDB(db)
|
||||
return i
|
||||
}
|
||||
|
||||
type inquiryDo struct{ gen.DO }
|
||||
|
||||
func (i inquiryDo) Debug() *inquiryDo {
|
||||
return i.withDO(i.DO.Debug())
|
||||
}
|
||||
|
||||
func (i inquiryDo) WithContext(ctx context.Context) *inquiryDo {
|
||||
return i.withDO(i.DO.WithContext(ctx))
|
||||
}
|
||||
|
||||
func (i inquiryDo) ReadDB() *inquiryDo {
|
||||
return i.Clauses(dbresolver.Read)
|
||||
}
|
||||
|
||||
func (i inquiryDo) WriteDB() *inquiryDo {
|
||||
return i.Clauses(dbresolver.Write)
|
||||
}
|
||||
|
||||
func (i inquiryDo) Session(config *gorm.Session) *inquiryDo {
|
||||
return i.withDO(i.DO.Session(config))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Clauses(conds ...clause.Expression) *inquiryDo {
|
||||
return i.withDO(i.DO.Clauses(conds...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Returning(value interface{}, columns ...string) *inquiryDo {
|
||||
return i.withDO(i.DO.Returning(value, columns...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Not(conds ...gen.Condition) *inquiryDo {
|
||||
return i.withDO(i.DO.Not(conds...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Or(conds ...gen.Condition) *inquiryDo {
|
||||
return i.withDO(i.DO.Or(conds...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Select(conds ...field.Expr) *inquiryDo {
|
||||
return i.withDO(i.DO.Select(conds...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Where(conds ...gen.Condition) *inquiryDo {
|
||||
return i.withDO(i.DO.Where(conds...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Order(conds ...field.Expr) *inquiryDo {
|
||||
return i.withDO(i.DO.Order(conds...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Distinct(cols ...field.Expr) *inquiryDo {
|
||||
return i.withDO(i.DO.Distinct(cols...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Omit(cols ...field.Expr) *inquiryDo {
|
||||
return i.withDO(i.DO.Omit(cols...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Join(table schema.Tabler, on ...field.Expr) *inquiryDo {
|
||||
return i.withDO(i.DO.Join(table, on...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) LeftJoin(table schema.Tabler, on ...field.Expr) *inquiryDo {
|
||||
return i.withDO(i.DO.LeftJoin(table, on...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) RightJoin(table schema.Tabler, on ...field.Expr) *inquiryDo {
|
||||
return i.withDO(i.DO.RightJoin(table, on...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Group(cols ...field.Expr) *inquiryDo {
|
||||
return i.withDO(i.DO.Group(cols...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Having(conds ...gen.Condition) *inquiryDo {
|
||||
return i.withDO(i.DO.Having(conds...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Limit(limit int) *inquiryDo {
|
||||
return i.withDO(i.DO.Limit(limit))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Offset(offset int) *inquiryDo {
|
||||
return i.withDO(i.DO.Offset(offset))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *inquiryDo {
|
||||
return i.withDO(i.DO.Scopes(funcs...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Unscoped() *inquiryDo {
|
||||
return i.withDO(i.DO.Unscoped())
|
||||
}
|
||||
|
||||
func (i inquiryDo) Create(values ...*models.Inquiry) error {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
return i.DO.Create(values)
|
||||
}
|
||||
|
||||
func (i inquiryDo) CreateInBatches(values []*models.Inquiry, batchSize int) error {
|
||||
return i.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 (i inquiryDo) Save(values ...*models.Inquiry) error {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
return i.DO.Save(values)
|
||||
}
|
||||
|
||||
func (i inquiryDo) First() (*models.Inquiry, error) {
|
||||
if result, err := i.DO.First(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*models.Inquiry), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i inquiryDo) Take() (*models.Inquiry, error) {
|
||||
if result, err := i.DO.Take(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*models.Inquiry), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i inquiryDo) Last() (*models.Inquiry, error) {
|
||||
if result, err := i.DO.Last(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*models.Inquiry), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i inquiryDo) Find() ([]*models.Inquiry, error) {
|
||||
result, err := i.DO.Find()
|
||||
return result.([]*models.Inquiry), err
|
||||
}
|
||||
|
||||
func (i inquiryDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.Inquiry, err error) {
|
||||
buf := make([]*models.Inquiry, 0, batchSize)
|
||||
err = i.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 (i inquiryDo) FindInBatches(result *[]*models.Inquiry, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||
return i.DO.FindInBatches(result, batchSize, fc)
|
||||
}
|
||||
|
||||
func (i inquiryDo) Attrs(attrs ...field.AssignExpr) *inquiryDo {
|
||||
return i.withDO(i.DO.Attrs(attrs...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Assign(attrs ...field.AssignExpr) *inquiryDo {
|
||||
return i.withDO(i.DO.Assign(attrs...))
|
||||
}
|
||||
|
||||
func (i inquiryDo) Joins(fields ...field.RelationField) *inquiryDo {
|
||||
for _, _f := range fields {
|
||||
i = *i.withDO(i.DO.Joins(_f))
|
||||
}
|
||||
return &i
|
||||
}
|
||||
|
||||
func (i inquiryDo) Preload(fields ...field.RelationField) *inquiryDo {
|
||||
for _, _f := range fields {
|
||||
i = *i.withDO(i.DO.Preload(_f))
|
||||
}
|
||||
return &i
|
||||
}
|
||||
|
||||
func (i inquiryDo) FirstOrInit() (*models.Inquiry, error) {
|
||||
if result, err := i.DO.FirstOrInit(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*models.Inquiry), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i inquiryDo) FirstOrCreate() (*models.Inquiry, error) {
|
||||
if result, err := i.DO.FirstOrCreate(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.(*models.Inquiry), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i inquiryDo) FindByPage(offset int, limit int) (result []*models.Inquiry, count int64, err error) {
|
||||
result, err = i.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 = i.Offset(-1).Limit(-1).Count()
|
||||
return
|
||||
}
|
||||
|
||||
func (i inquiryDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||
count, err = i.Count()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = i.Offset(offset).Limit(limit).Scan(result)
|
||||
return
|
||||
}
|
||||
|
||||
func (i inquiryDo) Scan(result interface{}) (err error) {
|
||||
return i.DO.Scan(result)
|
||||
}
|
||||
|
||||
func (i inquiryDo) Delete(models ...*models.Inquiry) (result gen.ResultInfo, err error) {
|
||||
return i.DO.Delete(models)
|
||||
}
|
||||
|
||||
func (i *inquiryDo) withDO(do gen.Dao) *inquiryDo {
|
||||
i.DO = *do.(*gen.DO)
|
||||
return i
|
||||
}
|
||||
@@ -73,6 +73,10 @@ func ApplyRouters(app *fiber.App) {
|
||||
edge.Post("/assign", handlers.AssignEdge)
|
||||
edge.Post("/all", handlers.AllEdgesAvailable)
|
||||
|
||||
// 其他系统接口
|
||||
inquiry := api.Group("/inquiry")
|
||||
inquiry.Post("/create", handlers.CreateInquiry)
|
||||
|
||||
// 回调
|
||||
callbacks := app.Group("/callback")
|
||||
callbacks.Get("/identify", handlers.IdentifyCallbackNew)
|
||||
|
||||
@@ -20,16 +20,16 @@ import (
|
||||
|
||||
// 通道服务
|
||||
var Channel = &channelServer{
|
||||
provider: &channelBaiyinService{},
|
||||
provider: &channelBaiyinProvider{},
|
||||
}
|
||||
|
||||
type ChanProviderAdapter interface {
|
||||
type ChannelServiceProvider interface {
|
||||
CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error)
|
||||
RemoveChannels(batch string) error
|
||||
}
|
||||
|
||||
type channelServer struct {
|
||||
provider ChanProviderAdapter
|
||||
provider ChannelServiceProvider
|
||||
}
|
||||
|
||||
func (s *channelServer) CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error) {
|
||||
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
"gorm.io/gen/field"
|
||||
)
|
||||
|
||||
type channelBaiyinService struct{}
|
||||
type channelBaiyinProvider struct{}
|
||||
|
||||
func (s *channelBaiyinService) 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, edgeFilter ...EdgeFilter) ([]*m.Channel, error) {
|
||||
var filter *EdgeFilter = nil
|
||||
if len(edgeFilter) > 0 {
|
||||
filter = &edgeFilter[0]
|
||||
@@ -256,7 +256,7 @@ func (s *channelBaiyinService) CreateChannels(source netip.Addr, resourceId int3
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
func (s *channelBaiyinService) RemoveChannels(batch string) error {
|
||||
func (s *channelBaiyinProvider) RemoveChannels(batch string) error {
|
||||
start := time.Now()
|
||||
|
||||
// 获取连接数据
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
g "platform/web/globals"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
"time"
|
||||
@@ -19,64 +18,67 @@ var Resource = &resourceService{}
|
||||
type resourceService struct{}
|
||||
|
||||
func (s *resourceService) CreateResourceByBalance(uid int32, now time.Time, data *CreateResourceData) error {
|
||||
return g.Redsync.WithLock(userBalanceKey(uid), func() error {
|
||||
return q.Q.Transaction(func(q *q.Query) error {
|
||||
// 找到用户
|
||||
user, err := q.User.
|
||||
Where(q.User.ID.Eq(uid)).
|
||||
Take()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查余额
|
||||
amount, err := data.GetAmount()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
balance := user.Balance.Sub(amount)
|
||||
if balance.IsNegative() {
|
||||
return ErrBalanceNotEnough
|
||||
}
|
||||
// 找到用户
|
||||
user, err := q.User.
|
||||
Where(q.User.ID.Eq(uid)).
|
||||
Take()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新用户余额
|
||||
_, err = q.User.
|
||||
Where(q.User.ID.Eq(uid), q.User.Balance.Eq(user.Balance)).
|
||||
UpdateSimple(q.User.Balance.Value(balance))
|
||||
if err != nil {
|
||||
return core.NewServErr("更新用户余额失败", err)
|
||||
}
|
||||
// 检查余额
|
||||
amount, err := data.GetAmount()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newBalance := user.Balance.Sub(amount)
|
||||
if newBalance.IsNegative() {
|
||||
return ErrBalanceNotEnough
|
||||
}
|
||||
|
||||
// 保存套餐
|
||||
resource, err := createResource(q, uid, now, data)
|
||||
if err != nil {
|
||||
return core.NewServErr("创建套餐失败", err)
|
||||
}
|
||||
return q.Q.Transaction(func(q *q.Query) error {
|
||||
|
||||
// 生成账单
|
||||
subject, err := data.GetSubject()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = q.Bill.Create(newForConsume(uid, Bill.GenNo(), subject, amount, resource))
|
||||
if err != nil {
|
||||
return core.NewServErr("生成账单失败", err)
|
||||
}
|
||||
// 更新用户余额
|
||||
_, err = q.User.
|
||||
Where(
|
||||
q.User.ID.Eq(uid),
|
||||
q.User.Balance.Eq(user.Balance),
|
||||
).
|
||||
UpdateSimple(q.User.Balance.Value(newBalance))
|
||||
if err != nil {
|
||||
return core.NewServErr("更新用户余额失败", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
// 保存套餐
|
||||
resource, err := createResource(q, uid, now, data)
|
||||
if err != nil {
|
||||
return core.NewServErr("创建套餐失败", err)
|
||||
}
|
||||
|
||||
// 生成账单
|
||||
subject, err := data.GetSubject()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = q.Bill.Create(newForConsume(uid, Bill.GenNo(), subject, amount, resource))
|
||||
if err != nil {
|
||||
return core.NewServErr("生成账单失败", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *resourceService) CreateResourceByTrade(uid int32, now time.Time, data *CreateResourceData, trade *m.Trade) error {
|
||||
func (s *resourceService) CreateResourceByTrade(uid int32, now time.Time, data *CreateResourceData, trade *m.Trade) error { // 检查交易
|
||||
if trade == nil {
|
||||
return core.NewBizErr("交易数据不能为空")
|
||||
}
|
||||
if trade.Status != m.TradeStatusSuccess {
|
||||
return core.NewBizErr("交易状态不正确")
|
||||
}
|
||||
|
||||
return q.Q.Transaction(func(q *q.Query) error {
|
||||
// 检查交易
|
||||
if trade == nil {
|
||||
return core.NewBizErr("交易数据不能为空")
|
||||
}
|
||||
if trade.Status != m.TradeStatusSuccess {
|
||||
return core.NewBizErr("交易状态不正确")
|
||||
}
|
||||
|
||||
// 保存套餐
|
||||
resource, err := createResource(q, uid, now, data)
|
||||
@@ -171,7 +173,7 @@ type CreateResourceData struct {
|
||||
type CreateShortResourceData struct {
|
||||
Live int32 `json:"live" validate:"required,min=180"`
|
||||
Mode m.ResourceMode `json:"mode" validate:"required"`
|
||||
Quota int32 `json:"quota"`
|
||||
Quota int32 `json:"quota" validate:"required"`
|
||||
Expire *int32 `json:"expire"`
|
||||
|
||||
name string
|
||||
@@ -182,7 +184,7 @@ type CreateLongResourceData struct {
|
||||
Live int32 `json:"live" validate:"required"`
|
||||
Mode m.ResourceMode `json:"mode" validate:"required"`
|
||||
Quota int32 `json:"quota" validate:"required"`
|
||||
Expire *int32 `json:"expire" validate:"required"`
|
||||
Expire *int32 `json:"expire"`
|
||||
|
||||
name string
|
||||
price *decimal.Decimal
|
||||
@@ -193,25 +195,25 @@ func (c *CreateResourceData) GetType() m.TradeType {
|
||||
}
|
||||
|
||||
func (c *CreateResourceData) GetSubject() (string, error) {
|
||||
switch c.Type {
|
||||
switch {
|
||||
default:
|
||||
return "", errors.New("无效的套餐类型")
|
||||
|
||||
case m.ResourceTypeShort:
|
||||
case c.Type == m.ResourceTypeShort && c.Short != nil:
|
||||
return c.Short.GetSubject()
|
||||
case m.ResourceTypeLong:
|
||||
case c.Type == m.ResourceTypeLong && c.Long != nil:
|
||||
return c.Long.GetSubject()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CreateResourceData) GetAmount() (decimal.Decimal, error) {
|
||||
switch c.Type {
|
||||
switch {
|
||||
default:
|
||||
return decimal.Zero, errors.New("无效的套餐类型")
|
||||
|
||||
case m.ResourceTypeShort:
|
||||
case c.Type == m.ResourceTypeShort && c.Short != nil:
|
||||
return c.Short.GetAmount()
|
||||
case m.ResourceTypeLong:
|
||||
case c.Type == m.ResourceTypeLong && c.Long != nil:
|
||||
return c.Long.GetAmount()
|
||||
}
|
||||
}
|
||||
@@ -339,6 +341,7 @@ func (data *CreateLongResourceData) GetAmount() (decimal.Decimal, error) {
|
||||
return *data.price, nil
|
||||
}
|
||||
|
||||
// 交易后创建套餐
|
||||
type ResourceOnTradeComplete struct{}
|
||||
|
||||
func (r ResourceOnTradeComplete) Check(t m.TradeType) (ProductInfo, bool) {
|
||||
@@ -352,6 +355,7 @@ func (r ResourceOnTradeComplete) OnTradeComplete(info ProductInfo, trade *m.Trad
|
||||
return Resource.CreateResourceByTrade(trade.UserID, time.Time(*trade.CompletedAt), info.(*CreateResourceData), trade)
|
||||
}
|
||||
|
||||
// 服务错误
|
||||
type ResourceServiceErr string
|
||||
|
||||
func (e ResourceServiceErr) Error() string {
|
||||
|
||||
Reference in New Issue
Block a user