Go实战全家桶之二十五:指标系统查询ES的业务通用封装

请求

type QueryShopReq struct {
    basedto.BaseEntity
    *requestfilter.RequestFilter
    model.ReportQueryRequest
}
type ReportQueryRequest struct {
    Type          string   `json:"type" doc:"member(我的)、shop(商户)"`
    ObjectType    string   `json:"object_type" doc:"扩展类型"`
    ObjectId      string   `json:"object_id"`
    ShopMemberId  int64    `json:"shop_member_id"`
    ShopMemberIds []string `json:"shop_member_ids"`
    ShopId        int64    `json:"shop_id"`
    DepartmentId  int64    `json:"department_id"`  //部门id
    DepartmentIds []string `json:"department_ids"` //部门ids,前端传的树形数组
    DeptIds       []int64  `json:"child_dept_ids"` //部门ids
    JobId         int64    `json:"job_id"`
    EmployeeId    int64    `json:"employee_id"` //员工id
    All           string   `json:"all"`
    StartTime     int64    `json:"start_time"`
    EndTime       int64    `json:"end_time"`
    Current       int64    `json:"current"`
    PageSize      int64    `json:"page_size"`
    OrderBy       string   `json:"order_by"`
    ScriptField   string   `json:"script_field"`
    PageCode      string   `json:"page_code"`
    MetricsCodes  []string `json:"metrics_codes"`
    LoginShopId   int64    `json:"user_shop_id"` //当前登录商户id
    ShopIds       []string `json:"shop_ids"`
    IsMerge       bool     `json:"is_merge"`
    Description   string   `json:"description"` //账户描述
    AccountIds    []int64  `json:"account_ids"`
}
type RequestFilter struct {
    Source     string   `json:"source"` //fields :"f1,f2,f3"
    FilterType int64    `json:"filter_type"`
    GtZeroAnd  []string `json:"gt_zero_and"` // both > 0
    GtZeroOr   []string `json:"gt_zero_or"`  //one is >0
}

func NewRequestFilter() *RequestFilter {
    return &RequestFilter{}
}

func (self *RequestFilter) WithFilterReq() *RequestFilter {
    self.FilterType = report_const.RET_FIELDS_FILTER_GTREQ
    return self
}
func (self *RequestFilter) WithFilterZero() *RequestFilter {
    self.FilterType = report_const.RET_FIELDS_FILTER_GTZERO
    return self
}
func (self *RequestFilter) WithFilterSimple() *RequestFilter {
    self.FilterType = report_const.RET_FIELDS_FILTER_SIMPLE
    return self
}
func (self *RequestFilter) IfFilter() bool {
    return self.Source != "" || report_const.RET_FIELDS_FILTER_DEFAULT == self.FilterType ||
       report_const.RET_FIELDS_FILTER_GTZERO == self.FilterType || report_const.RET_FIELDS_FILTER_GTREQ == self.FilterType
}
func (self *RequestFilter) IfFilterSource() bool {
    return self.Source != ""
}
func (self *RequestFilter) IfDefault() bool {
    return report_const.RET_FIELDS_FILTER_DEFAULT == self.FilterType

}

func (self *RequestFilter) WithFilterDefault() *RequestFilter {
    self.FilterType = report_const.RET_FIELDS_FILTER_DEFAULT
    return self
}

func (req *RequestFilter) Filter(q *elastic.BoolQuery) {
    if len(req.GtZeroOr) > 0 {
       var qor = elastic.NewBoolQuery()
       for _, item := range req.GtZeroOr {
          qor.Should(elastic.NewRangeQuery(item).Gt(0).Lt(1000000000000))
       }
       qor.MinimumNumberShouldMatch(1)
       q.Must(qor)
    }
    for _, i := range req.GtZeroAnd {
       q.Filter(elastic.NewRangeQuery(i).Gt(0).Lt(1000000000000))
    }
}

func (req *RequestFilter) MergeSource() string {
    var src = req.Source
    if len(req.GtZeroAnd) > 0 {
       src += "," + strings.Join(req.GtZeroAnd, ",")
    }
    if len(req.GtZeroOr) > 0 {
       src += "," + strings.Join(req.GtZeroOr, ",")
    }
    return src
}

api服务

package reportshopfront

import (
    "git.ichub.com/general/webcli120/goconfig/base/basedto"
    "git.ichub.com/general/webcli120/goconfig/base/jsonutils"
    "git.ichub.com/general/webcli120/goconfig/ichublog/golog"
    "git.ichub.com/general/webcli120/goweb/pagemodel"
    "git.ichub.com/general/webcli120/goweb/webclient/eswebclient/webfacade"
    "github.com/gogf/gf/v2/util/gconv"
    "github.com/olivere/elastic/v7"
    "icd/general-srv/esentity"
    "icd/general-srv/handler/report_base"
    "icd/general-srv/handler/report_const"
    "icd/general-srv/handler/report_dto/reportdtofront"
    "icd/general-srv/model"
    "icd/general-srv/third"
    "icd/utils"
    "time"
)
 
type ReportShopFront struct {
    basedto.BaseEntitySingle
    *report_base.ReportDayBase
}

func NewReportShopQuery() *ReportShopFront {
    return &ReportShopFront{
       ReportDayBase: report_base.NewReportDayBase(),
    }
}

func (self *ReportShopFront) getSumValue(bucket string, v any) int64 {
    var vv = v.(map[string]any)
    var ret = vv[bucket].(map[string]any)
    return gconv.Int64(ret["value"])
}

// 按商户分组
func (self *ReportShopFront) StatShopGroup(req *model.ReportQueryRequest) *pagemodel.PageResult[*esentity.ServiceReportDayEs] {
    //获取商户树
    _ = self.InitReqShopIds(req)
    //构造查询query条件
    var q = self.getQuery(req)

    var qq = webfacade.DefaultOf[*esentity.ServiceReportDayEs](q)
    qq.PageSize = 0
    self.BuildShopSumAgg(qq)
    var ret = qq.GeneralQuery()
    if ret.IsFailed() {
       golog.Error("StatShopGroup err =", ret)
       return ret
    }
    self.Bucket2Data(req, ret)
    return ret
}
func (self *ReportShopFront) Bucket2Data(req *model.ReportQueryRequest, ret *pagemodel.PageResult[*esentity.ServiceReportDayEs]) {
    var data = []*esentity.ServiceReportDayEs{}
    for _, bucket := range ret.Ret2Buckets("shop_id") {
       var m = &esentity.ServiceReportDayEs{}
       var v = bucket.(map[string]any)
       var sums = self.GetSumRet(v)
       jsonutils.Decode2Stru(sums, m)
       m.ShopId = gconv.Int64(v["key"])
       m.CreatedAt = req.StartTime
       m.CreatedDate = time.UnixMilli(req.StartTime)
       if self.IfLocalUTC() {
          m.CreatedDate = time.UnixMilli(req.StartTime + 8*3600000)
       }
       data = append(data, m)
    }
    ret.Data = data
}
func (self *ReportShopFront) GetSumRet(v map[string]any) map[string]int64 {
    var ret = map[string]int64{}
    for _, bucket := range self.GetSumBukchets() {
       ret[bucket] = self.getSumValue(bucket, v)
    }

    return ret
}
func (self *ReportShopFront) GetSumBukchets() []string {
    return []string{
       "rfq_count", "qfr_count", "avab_count", "question_count", "reply_count", "reply_count_notin", "bargaining_count", "pricing_count",
       "sale_amount", "sale_amount_self", "sale_amount_platform", "sale_amount_external", "sale_amount_external_cash", "sale_amount_external_no_cash",
       "sale_gross_profit_amount", "sale_gross_profit_amount_self", "sale_gross_profit_amount_platform", "sale_gross_profit_amount_external",
       "sale_return_amount", "sale_return_amount_self", "sale_return_amount_platform", "sale_return_amount_external",
       "purchase_amount", "purchase_amount_self", "purchase_amount_platform", "purchase_amount_external", "purchase_amount_external_cash", "purchase_amount_external_no_cash",
       "purchase_gross_profit_amount", "purchase_gross_profit_amount_self", "purchase_gross_profit_amount_platform", "purchase_gross_profit_amount_external",
       "purchase_return_amount", "purchase_return_amount_self", "purchase_return_amount_platform", "purchase_return_amount_external",
       "sale_count", "sale_count_cancel",
       "verfication_amount", "verfication_amount_self", "verfication_amount_platform", "verfication_amount_external",
       "verfication_gross_profit_amount", "verfication_gross_profit_amount_self", "verfication_gross_profit_amount_platform", "verfication_gross_profit_amount_external",
       "expense_amount", "expense_amount_self", "expense_amount_platform", "expense_amount_external",
    }
}
func (self *ReportShopFront) BuildShopSumAgg(qq *webfacade.WebFacade[*esentity.ServiceReportDayEs]) {
    var agg = elastic.NewTermsAggregation().Field("shop_id").Size(1000000)
    for _, bucket := range self.GetSumBukchets() {
       agg.SubAggregation(bucket, elastic.NewSumAggregation().Field(bucket))
    }
    qq.Aggregation("shop_id", agg)
}

// query() size>0 default=0
func (self *ReportShopFront) Query(req *model.ReportQueryRequest) *pagemodel.PageResult[*esentity.ServiceReportDayEs] {
    //获取商户树
    self.InitReqShopIds(req)
    //构造查询query条件
    var q = self.getQuery(req)

    var qq = webfacade.DefaultOf[*esentity.ServiceReportDayEs](q)
    qq.PageSize = gconv.Int(req.PageSize)
    qq.PageCurrent = gconv.Int(req.Current)
    var ret = qq.GeneralQuery()
    if ret.IsFailed() {
       golog.Error("Query err =", ret)

    }
    //var data = []*model.ReportQueryResultModel{}
    return ret
}
func (self *ReportShopFront) InitReqShopIds(req *model.ReportQueryRequest) map[int64]int64 {

    if req.ShopIds == nil {
       req.ShopIds = []string{}
    }
    var rets = map[int64]int64{}

    if req.ShopId > 0 {
       req.ShopIds = append(req.ShopIds, gconv.String(req.ShopId))
       var childIds, err = third.FindBeanThirdProxy().ContactShopGetChildIdsByRecursion(req.ShopId)
       if err != nil {
          golog.Error("ReportShopFront [InitReqShopIds] err =", err)
       } else {
          req.ShopIds = append(req.ShopIds, gconv.SliceStr(childIds)...)
       }
       req.ShopId = 0
       rets[req.ShopId] = 0
       for _, id := range childIds {
          rets[id] = req.ShopId
       }
    }
    return rets

}
func (self *ReportShopFront) getQuery(req *model.ReportQueryRequest) *elastic.BoolQuery {
    q := elastic.NewBoolQuery()
    if req.StartTime > 0 {
       q.Must(elastic.NewRangeQuery("created_at").Gte(req.StartTime))
    }
    if req.EndTime > 0 {
       q.Must(elastic.NewRangeQuery("created_at").Lte(req.EndTime))
    }
    if req.ShopId > 0 {
       q.Must(elastic.NewTermQuery("shop_id", req.ShopId))
    }
    if req.ShopMemberId > 0 {
       q.Must(elastic.NewTermQuery("shop_member_id", req.ShopMemberId))
    }
    if req.EmployeeId > 0 {
       q.Must(elastic.NewTermQuery("employee_id", req.EmployeeId))
    }
    if len(req.ShopMemberIds) > 0 {
       q.Must(elastic.NewTermsQuery("shop_member_id", utils.StringArrToInterface(req.ShopMemberIds)...))
    }
    if len(req.ShopIds) > 0 {
       q.Filter(elastic.NewTermsQuery("shop_id", utils.StringArrToInterface(req.ShopIds)...))
    }
    if req.ObjectType == "account" {
       //if len(req.AccountIds) > 0 {
       // q.Must(elastic.NewTermsQuery("account_id", utils.Int64ArrToInterface(req.AccountIds)...))
       //}
       if req.Description != "" {
          bs := elastic.NewBoolQuery()
          bs.Should(elastic.NewRegexpQuery("account_name", ".*"+req.Description+".*"), elastic.NewRegexpQuery("account_number", ".*"+req.Description+".*"))
          q.Must(bs)
       }
    }

    if req.All != "" {
       self.getAllQuery(req.ObjectType, req.All, q)
    }
    if req.ObjectType != "" && req.ObjectId != "" {
       self.getObjectQuery(req.ObjectType, req.ObjectId, q)
    }
    return q
}

// 获取通用查询条件
func (self *ReportShopFront) getAllQuery(t, all string, q *elastic.BoolQuery) {
    switch t {
    case "member": //成员
       q.Must(elastic.NewRegexpQuery("shop_member_name", ".*"+all+".*"))
    case "buyer": //下单人
       bs := elastic.NewBoolQuery()
       bs.Should(elastic.NewRegexpQuery("account_contact_name", ".*"+all+".*"), elastic.NewRegexpQuery("account_name", ".*"+all+".*"))
       q.Must(bs)
    case "account": //账户
       bs := elastic.NewBoolQuery()
       bs.Should(elastic.NewRegexpQuery("account_name", ".*"+all+".*"), elastic.NewRegexpQuery("account_number", ".*"+all+".*"))
       q.Must(bs)
    case "sku": //库存项
       bs := elastic.NewBoolQuery()
       bs.Should(elastic.NewRegexpQuery("part_number_raw", ".*"+all+".*"), elastic.NewRegexpQuery("brand_name", ".*"+all+".*"), elastic.NewRegexpQuery("brand_short_name", ".*"+all+".*"), elastic.NewRegexpQuery("shop_sku_type_name", ".*"+all+".*"))
       q.Must(bs)
    case "shopSpu": //商品
       bs := elastic.NewBoolQuery()
       bs.Should(elastic.NewRegexpQuery("part_number_raw", ".*"+all+".*"), elastic.NewRegexpQuery("brand_name", ".*"+all+".*"), elastic.NewRegexpQuery("brand_short_name", ".*"+all+".*"))
       q.Must(bs)
    case "brand": //厂牌
       bs := elastic.NewBoolQuery()
       bs.Should(elastic.NewRegexpQuery("brand_name", ".*"+all+".*"), elastic.NewRegexpQuery("brand_short_name", ".*"+all+".*"))
       q.Must(bs)
    case "category": //类目
       q.Must(elastic.NewRegexpQuery("categ_name", ".*"+all+".*"), elastic.NewRegexpQuery("categ_short_name", ".*"+all+".*"))
    case "employee": //员工
       q.Must(elastic.NewRegexpQuery("employee_name", ".*"+all+".*"))
    case "department": //部门
       q.Must(elastic.NewRegexpQuery("department_name", ".*"+all+".*"))
    }
}

// 详细页赋值object_id
func (self *ReportShopFront) getObjectQuery(objectType, objectId string, q *elastic.BoolQuery) {
    switch objectType {
    case "member": //成员
       q.Must(elastic.NewTermQuery("shop_member_id", objectId))
    case "shop": //商户
       q.Must(elastic.NewTermQuery("shop_id", objectId))
    case "department": //部门
       q.Must(elastic.NewTermQuery("department_id", objectId))
    case "employee": //员工
       q.Must(elastic.NewTermQuery("employee_id", objectId))
    case "buyer": //下单人
       q.Must(elastic.NewTermQuery("account_contact_id", objectId))
    case "account": //账户
       q.Must(elastic.NewTermQuery("account_id", objectId))
    case "sku": //库存项
       q.Must(elastic.NewTermQuery("shop_sku_id", objectId))
    case "shopSpu": //商品
       q.Must(elastic.NewTermQuery("shop_spu_id", objectId))
    case "brand": //厂牌
       q.Must(elastic.NewTermQuery("brand_id", objectId))
    case "category": //类目
       q.Must(elastic.NewTermQuery("categ_id", objectId))
    case "contact": //人脉联系人
       q.Must(elastic.NewTermQuery("contact_id", objectId))
    case "ichub": //平台
       q.Must(elastic.NewTermQuery("shop_member_id", objectId))
    }
}
func (self *ReportShopFront) Fill(ret *pagemodel.PageResult[*esentity.ServiceReportDayEs]) {
    if ret.IsFailed() {
       golog.Error("Query err =", ret)
       return
    }

}
func (self *ReportShopFront) QueryShop(req *reportdtofront.QueryShopReq) *pagemodel.PageResult[*esentity.ServiceReportDayEs] {
    //获取商户树
    self.InitReqShopIds(&req.ReportQueryRequest)
    //构造查询query条件
    var q = self.getQuery(&req.ReportQueryRequest)
    req.Filter(q)

    var qq = webfacade.DefaultOf[*esentity.ServiceReportDayEs](q)
    qq.PageSize = gconv.Int(req.PageSize)
    qq.PageCurrent = gconv.Int(req.Current)
    var ret = qq.GeneralQuery()

    self.Fill(ret)
    return ret
}
func (self *ReportShopFront) SumData(ret *pagemodel.PageResult[*esentity.ServiceReportDayEs]) {

    var sum = &esentity.ServiceReportDayEs{}
    var mapsret = self.SumDataMap(gconv.SliceAny(ret.Data))

    gconv.Struct(mapsret, sum)
    ret.Data = []*esentity.ServiceReportDayEs{sum}
}
func (self *ReportShopFront) queryMap(req *reportdtofront.QueryShopReq) *pagemodel.PageResult[map[string]any] {
    var ret = self.QueryShop(req)
    var rets = pagemodel.NewPageResult[map[string]any]()
    rets.FromPageResult(ret.PageResult)
    if ret.IsSuccess() {
       gconv.Structs(ret.Data, &rets.Data)
    }
    return rets
}

func (self *ReportShopFront) QueryShopTotal(req *reportdtofront.QueryShopReq) *pagemodel.PageResult[*esentity.ServiceReportDayEs] {

    var ret = self.QueryShop(req)
    if ret.IsSuccess() {
       self.SumData(ret)
    }
    return ret
}
func (self *ReportShopFront) queryTotalMap(req *reportdtofront.QueryShopReq) *pagemodel.PageResult[map[string]any] {
    var ret = self.QueryShopTotal(req)
    var rets = pagemodel.NewPageResult[map[string]any]()
    rets.FromPageResult(ret.PageResult)
    if ret.IsSuccess() {
       gconv.Structs(ret.Data, &rets.Data)
    }
    return rets
}
func (self *ReportShopFront) QueryFilter(req *reportdtofront.QueryShopReq) *pagemodel.PageResult[map[string]any] {
    var ret = self.queryMap(req)
    self.qFilter(req, ret)
    return ret
}

func (self *ReportShopFront) qFilter(req *reportdtofront.QueryShopReq, ret *pagemodel.PageResult[map[string]any]) {

    if req.IfFilterSource() {
       self.FilterSource(ret.Data, req.MergeSource())
    }
    if req.FilterType == report_const.RET_FIELDS_FILTER_GTZERO {
       self.FilterZero(ret.Data)
    }
    if req.FilterType == report_const.RET_FIELDS_FILTER_GTREQ {

       self.FilterReq(ret.Data, req.GtZeroAnd, req.GtZeroOr)
    }

}
func (self *ReportShopFront) QueryTotalFilter(req *reportdtofront.QueryShopReq) *pagemodel.PageResult[map[string]any] {
    var ret = self.queryTotalMap(req)
    self.qFilter(req, ret)
    return ret
} 
type RequestFilter struct {
    Source     string   `json:"source"` //fields :"f1,f2,f3"
    FilterType int64    `json:"filter_type"`
    GtZeroAnd  []string `json:"gt_zero_and"` // both > 0
    GtZeroOr   []string `json:"gt_zero_or"`  //one is >0
}

func NewRequestFilter() *RequestFilter {
    return &RequestFilter{}
}

func (self *RequestFilter) WithFilterReq() *RequestFilter {
    self.FilterType = report_const.RET_FIELDS_FILTER_GTREQ
    return self
}
func (self *RequestFilter) WithFilterZero() *RequestFilter {
    self.FilterType = report_const.RET_FIELDS_FILTER_GTZERO
    return self
}
func (self *RequestFilter) WithFilterSimple() *RequestFilter {
    self.FilterType = report_const.RET_FIELDS_FILTER_SIMPLE
    return self
}
func (self *RequestFilter) IfFilter() bool {
    return self.Source != "" || report_const.RET_FIELDS_FILTER_DEFAULT == self.FilterType ||
       report_const.RET_FIELDS_FILTER_GTZERO == self.FilterType || report_const.RET_FIELDS_FILTER_GTREQ == self.FilterType
}
func (self *RequestFilter) IfFilterSource() bool {
    return self.Source != ""
}
func (self *RequestFilter) IfDefault() bool {
    return report_const.RET_FIELDS_FILTER_DEFAULT == self.FilterType

}

func (self *RequestFilter) WithFilterDefault() *RequestFilter {
    self.FilterType = report_const.RET_FIELDS_FILTER_DEFAULT
    return self
}

func (req *RequestFilter) Filter(q *elastic.BoolQuery) {
    if len(req.GtZeroOr) > 0 {
       var qor = elastic.NewBoolQuery()
       for _, item := range req.GtZeroOr {
          qor.Should(elastic.NewRangeQuery(item).Gt(0).Lt(1000000000000))
       }
       qor.MinimumNumberShouldMatch(1)
       q.Must(qor)
    }
    for _, i := range req.GtZeroAnd {
       q.Filter(elastic.NewRangeQuery(i).Gt(0).Lt(1000000000000))
    }
}

func (req *RequestFilter) MergeSource() string {
    var src = req.Source
    if len(req.GtZeroAnd) > 0 {
       src += "," + strings.Join(req.GtZeroAnd, ",")
    }
    if len(req.GtZeroOr) > 0 {
       src += "," + strings.Join(req.GtZeroOr, ",")
    }
    return src
}

测试
func (self *TestGeneralReportShopFrontSuite) Test012_QueryShopFilterSoyrceReq() {

    var req = reportdtofront.NewQueryShopReq()
    req.PageSize = 100
    req.StartTime = 1733932800000
    req.EndTime = 1734105599000 //1734019199000    //req.ShopId = 673093338758873089 // 673093338760839169
    req.ShopIds = gconv.SliceStr([]int64{673093338758873089, 891856612872454145, 673093338758873089, 828505516360433665,
       891856612872454145, 673093338758873089, 673093338760839169, 891856612872454145})
    req.GtZeroOr = []string{"avab_count", "pricing_count"} //req.GtZeroAnd = []string{"avab_count", "pricing_count"}
 
    req.Source = "shop_id,shop_name,avab_count"
    var retsum = self.inst.QueryFilter(req)

    golog.Info(retsum)
}

结果

INFO[2024-12-1215 09:11:14]C:/Users/admin/go/pkg/mod/git.ichub.com/general/webcli120@v1.0.994-dev-12/goconfig/base/goutils/go_log.go:88 git.ichub.com/general/webcli120/goconfig/base/goutils.Info() {
     "baseEntity": {},
     "code": 200,
     "msg": "成功",
     "request_id": "cce95fda-aca7-422b-ba00-00b3211edfcb",
     "hosturl": "http://192.168.27.144:88/query",
     "total": 8,
     "page_size": 100,
     "current": 1,
     "exist": false,
     "cmd_name": "ES_HTTPCLI_QUERY_SOURCE",
     "cmd_type": 39,
     "data_highlight": [],
     "data": [
          {
               "shop_name": "勾股业务",
               "shop_id": 673093338758873089,
               "pricing_count": 2,
               "avab_count": 3
          },
          {
               "shop_name": "有芯华南",
               "pricing_count": 0,
               "avab_count": 1,
               "shop_id": 673093338760839169
          },
          {
               "shop_name": "勾股业务",
               "avab_count": 0,
               "shop_id": 673093338758873089,
               "pricing_count": 1
          },
          {
               "shop_id": 673093338758873089,
               "pricing_count": 0,
               "avab_count": 2,
               "shop_name": "勾股业务"
          },
          {
               "shop_id": 828505516360433665,
               "shop_name": "勾股弦技术",
               "avab_count": 1,
               "pricing_count": 0
          },
          {
               "avab_count": 8,
               "shop_id": 891856612872454145,
               "pricing_count": 0,
               "shop_name": "韦尔股份"
          },
          {
               "shop_id": 891856612872454145,
               "shop_name": "韦尔股份",
               "avab_count": 6,
               "pricing_count": 0
          },
          {
               "pricing_count": 0,
               "shop_id": 891856612872454145,
               "shop_name": "韦尔股份",
               "avab_count": 3
          }
     ]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

leijmdas

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值