package controllers import ( "encoding/json" "fmt" "io" "strconv" "strings" "time" "server/models" "server/services" beego "github.com/beego/beego/v2/server/web" ) // PlatformProductController 平台端「产品定价」管理 // // 产品(名称 / 编码 / 价格 / 关联功能 / 上下架)+ 功能节点树 -> 租户端购买展示与下单。 type PlatformProductController struct { beego.Controller } func (c *PlatformProductController) jsonErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func (c *PlatformProductController) jsonOK(data interface{}, msg string) { if msg == "" { msg = "success" } resp := map[string]interface{}{"code": 200, "msg": msg} if data != nil { resp["data"] = data } c.Data["json"] = resp _ = c.ServeJSON() } func (c *PlatformProductController) readBody(target interface{}) bool { raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return false } if err := json.Unmarshal(raw, target); err != nil { c.jsonErr(400, 400, "参数错误") return false } return true } // ============================ 产品 ============================ type productFeaturePayload struct { ID uint64 `json:"id"` Pid uint64 `json:"pid"` Title string `json:"title"` Description string `json:"description"` Sort int `json:"sort"` } type productPayload struct { Code string `json:"code"` Name string `json:"name"` Price *float64 `json:"price"` ModuleCode string `json:"module_code"` Sort *int `json:"sort"` Status *int8 `json:"status"` Remark string `json:"remark"` Features []productFeaturePayload `json:"features"` } // loadProductFeatures 批量加载产品功能节点(扁平,含 pid,前端按 pid 组装树) func loadProductFeatures(productIDs []uint64) map[uint64][]models.PlatformProductFeature { result := map[uint64][]models.PlatformProductFeature{} if len(productIDs) == 0 { return result } var rows []models.PlatformProductFeature if _, err := models.Orm.QueryTable(new(models.PlatformProductFeature)). Filter("product_id__in", productIDs). OrderBy("sort", "id"). All(&rows); err != nil { return result } for _, r := range rows { result[r.ProductID] = append(result[r.ProductID], r) } return result } func productToMap(p models.PlatformProduct, features []models.PlatformProductFeature) map[string]interface{} { if features == nil { features = []models.PlatformProductFeature{} } return map[string]interface{}{ "id": p.ID, "code": p.Code, "name": p.Name, "price": p.Price, "module_code": p.ModuleCode, "sort": p.Sort, "status": p.Status, "remark": p.Remark, "features": features, "create_time": p.CreateTime, "update_time": p.UpdateTime, } } // GetProductList GET /platform/product/list func (c *PlatformProductController) GetProductList() { keyword := strings.TrimSpace(c.GetString("keyword")) status := strings.TrimSpace(c.GetString("status")) qs := models.Orm.QueryTable(new(models.PlatformProduct)).Filter("delete_time__isnull", true) if keyword != "" { qs = qs.Filter("name__icontains", keyword) } if status == "0" || status == "1" { v, _ := strconv.Atoi(status) qs = qs.Filter("status", v) } var rows []models.PlatformProduct if _, err := qs.OrderBy("sort", "id").All(&rows); err != nil { c.jsonErr(500, 500, "获取失败:"+err.Error()) return } ids := make([]uint64, 0, len(rows)) for _, r := range rows { ids = append(ids, r.ID) } featureMap := loadProductFeatures(ids) list := make([]map[string]interface{}, 0, len(rows)) for _, r := range rows { list = append(list, productToMap(r, featureMap[r.ID])) } c.jsonOK(map[string]interface{}{"list": list, "total": len(list)}, "获取成功") } // GetProductDetail GET /platform/product/detail/:id func (c *PlatformProductController) GetProductDetail() { id := parseUint64Param(c.Ctx.Input.Param(":id")) if id == 0 { c.jsonErr(400, 400, "参数错误") return } var row models.PlatformProduct if err := models.Orm.QueryTable(new(models.PlatformProduct)). Filter("id", id). Filter("delete_time__isnull", true). One(&row); err != nil { c.jsonErr(404, 404, "产品不存在") return } c.jsonOK(productToMap(row, loadProductFeatures([]uint64{id})[id]), "获取成功") } // saveProductFeatures 覆盖式保存功能节点(含父子关系 pid) // 前端传扁平数组,顶级节点 pid=0;子节点的 pid 指向父节点在库中的真实 ID, // 因此这里先按「前端临时序号 -> 库 ID」建立映射再落库。 func saveProductFeatures(productID uint64, items []productFeaturePayload) error { if _, err := models.Orm.QueryTable(new(models.PlatformProductFeature)). Filter("product_id", productID).Delete(); err != nil { return err } if len(items) == 0 { return nil } // 第一遍:按输入顺序建节点,记录 oldID(库中已有ID或前端临时ID) -> newID oldToNew := map[uint64]uint64{} type pending struct { item productFeaturePayload newID uint64 } created := make([]pending, 0, len(items)) for i, it := range items { title := strings.TrimSpace(it.Title) if title == "" { continue } sort := it.Sort if sort == 0 { sort = i + 1 } row := &models.PlatformProductFeature{ ProductID: productID, Title: title, Description: strings.TrimSpace(it.Description), Sort: sort, } id, err := models.Orm.Insert(row) if err != nil { return err } // 前端用 id 字段标识节点:已存在节点传库中真实ID,新增节点传负数临时ID, // 统一建立「旧标识 -> 新ID」映射,供子节点回填 pid。 if it.ID != 0 { oldToNew[it.ID] = uint64(id) } created = append(created, pending{item: it, newID: uint64(id)}) } // 第二遍:回填父级 for _, p := range created { pid := p.item.Pid if pid == 0 { continue } if newPid, ok := oldToNew[pid]; ok { pid = newPid } // 父节点已被删除或指向自身时降级为顶级 if pid == p.newID { pid = 0 } if _, err := models.Orm.QueryTable(new(models.PlatformProductFeature)). Filter("id", p.newID). Update(map[string]interface{}{"pid": pid}); err != nil { return err } } return nil } // CreateProduct POST /platform/product/create func (c *PlatformProductController) CreateProduct() { var p productPayload if !c.readBody(&p) { return } p.Code = strings.TrimSpace(p.Code) p.Name = strings.TrimSpace(p.Name) if p.Name == "" { c.jsonErr(400, 400, "产品名称不能为空") return } if p.Code == "" { c.jsonErr(400, 400, "产品编码不能为空") return } cnt, _ := models.Orm.QueryTable(new(models.PlatformProduct)). Filter("code", p.Code). Filter("delete_time__isnull", true). Count() if cnt > 0 { c.jsonErr(400, 400, "产品编码已存在") return } now := time.Now() row := &models.PlatformProduct{ Code: p.Code, Name: p.Name, Price: valueFloat(p.Price, 0), ModuleCode: strings.TrimSpace(p.ModuleCode), Sort: valueInt(p.Sort, 0), Status: valueInt8(p.Status, 1), Remark: strings.TrimSpace(p.Remark), UpdateTime: &now, } id, err := models.Orm.Insert(row) if err != nil { c.jsonErr(500, 500, "创建失败:"+err.Error()) return } if p.Features != nil { if err := saveProductFeatures(uint64(id), p.Features); err != nil { c.jsonErr(500, 500, "功能节点保存失败:"+err.Error()) return } } c.jsonOK(map[string]interface{}{"id": uint64(id)}, "创建成功") } // EditProduct POST /platform/product/edit/:id func (c *PlatformProductController) EditProduct() { id := parseUint64Param(c.Ctx.Input.Param(":id")) if id == 0 { c.jsonErr(400, 400, "参数错误") return } var p productPayload if !c.readBody(&p) { return } p.Code = strings.TrimSpace(p.Code) p.Name = strings.TrimSpace(p.Name) if p.Name == "" { c.jsonErr(400, 400, "产品名称不能为空") return } if p.Code != "" { cnt, _ := models.Orm.QueryTable(new(models.PlatformProduct)). Filter("code", p.Code). Filter("id__ne", id). Filter("delete_time__isnull", true). Count() if cnt > 0 { c.jsonErr(400, 400, "产品编码已存在") return } } now := time.Now() update := map[string]interface{}{ "name": p.Name, "module_code": strings.TrimSpace(p.ModuleCode), "sort": valueInt(p.Sort, 0), "status": valueInt8(p.Status, 1), "remark": strings.TrimSpace(p.Remark), "update_time": now, } if p.Code != "" { update["code"] = p.Code } if p.Price != nil { update["price"] = *p.Price } if _, err := models.Orm.QueryTable(new(models.PlatformProduct)). Filter("id", id). Filter("delete_time__isnull", true). Update(update); err != nil { c.jsonErr(500, 500, "编辑失败:"+err.Error()) return } if p.Features != nil { if err := saveProductFeatures(id, p.Features); err != nil { c.jsonErr(500, 500, "功能节点保存失败:"+err.Error()) return } } c.jsonOK(nil, "编辑成功") } // DeleteProduct DELETE /platform/product/delete/:id(软删) func (c *PlatformProductController) DeleteProduct() { id := parseUint64Param(c.Ctx.Input.Param(":id")) if id == 0 { c.jsonErr(400, 400, "参数错误") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.PlatformProduct)). Filter("id", id). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now, "update_time": now}) if err != nil { c.jsonErr(500, 500, "删除失败:"+err.Error()) return } if n == 0 { c.jsonErr(404, 404, "产品不存在") return } _, _ = models.Orm.QueryTable(new(models.PlatformProductFeature)).Filter("product_id", id).Delete() c.jsonOK(nil, "删除成功") } // ============================ 购买订单 ============================ // GetProductOrders GET /platform/product/orders?status=&keyword= func (c *PlatformProductController) GetProductOrders() { status := strings.TrimSpace(c.GetString("status")) keyword := strings.TrimSpace(c.GetString("keyword")) qs := models.Orm.QueryTable(new(models.PlatformProductOrder)).Filter("delete_time__isnull", true) if status != "" { qs = qs.Filter("status", status) } if keyword != "" { qs = qs.Filter("product_name__icontains", keyword) } var rows []models.PlatformProductOrder if _, err := qs.OrderBy("-id").Limit(200).All(&rows); err != nil { c.jsonErr(500, 500, "获取失败:"+err.Error()) return } c.jsonOK(map[string]interface{}{"list": rows, "total": len(rows)}, "获取成功") } // ActivateProductOrder POST /platform/product/order/activate/:id // 手动开通:将订单置为已支付并绑定套餐(支付回调失败/线下付款时使用) func (c *PlatformProductController) ActivateProductOrder() { id := parseUint64Param(c.Ctx.Input.Param(":id")) if id == 0 { c.jsonErr(400, 400, "参数错误") return } var order models.PlatformProductOrder if err := models.Orm.QueryTable(new(models.PlatformProductOrder)). Filter("id", id). Filter("delete_time__isnull", true). One(&order); err != nil { c.jsonErr(404, 404, "订单不存在") return } if order.Status == models.ProductOrderStatusActivated { c.jsonErr(400, 400, "订单已开通") return } if err := activateProductOrder(&order, "平台手动开通"); err != nil { c.jsonErr(400, 400, err.Error()) return } c.jsonOK(nil, "开通成功") } // activateProductOrder 订单开通:绑定套餐(产品配置了套餐时)并置为已开通 func activateProductOrder(order *models.PlatformProductOrder, operator string) error { if order.PackageID > 0 && order.Tid > 0 { if _, err := services.ApplyPackageToTenant(order.Tid, order.PackageID, true); err != nil { return fmt.Errorf("开通失败:%v", err) } } now := time.Now() order.Status = models.ProductOrderStatusActivated order.ActivateTime = &now order.UpdateTime = &now order.Remark = strings.TrimSpace(order.Remark + " " + operator) _, err := models.Orm.Update(order, "Status", "ActivateTime", "Remark", "UpdateTime") return err }