Kaynağa Gözat

适配插件安装

yxh 4 yıl önce
ebeveyn
işleme
3b167f82b5

+ 142 - 0
app/system/api/plugins_manage.go

@@ -0,0 +1,142 @@
+// ==========================================================================
+// GFast自动生成控制器相关代码,只生成一次,按需修改,再次生成不会覆盖.
+// 生成日期:2021-08-31 17:58:43
+// 生成路径: gfast/app/system/api/plugins_manage.go
+// 生成人:gfast
+// ==========================================================================
+
+package api
+
+import (
+	"gfast/app/system/dao"
+	"gfast/app/system/service"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/text/gstr"
+	"github.com/gogf/gf/util/gvalid"
+)
+
+type pluginsManage struct {
+	SystemBase
+}
+
+var PluginsManage = new(pluginsManage)
+
+// List 列表
+func (c *pluginsManage) List(r *ghttp.Request) {
+	var req *dao.PluginsManageSearchReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		c.FailJsonExit(r, err.(gvalid.Error).FirstString())
+	}
+	req.Ctx = r.GetCtx()
+	total, page, list, err := service.PluginsManage.GetList(req)
+	if err != nil {
+		c.FailJsonExit(r, err.Error())
+	}
+	result := g.Map{
+		"currentPage": page,
+		"total":       total,
+		"list":        list,
+	}
+	c.SusJsonExit(r, result)
+}
+
+// Get 获取
+func (c *pluginsManage) Get(r *ghttp.Request) {
+	id := r.GetInt64("id")
+	info, err := service.PluginsManage.GetInfoById(r.GetCtx(), id)
+	if err != nil {
+		c.FailJsonExit(r, err.Error())
+	}
+	c.SusJsonExit(r, info)
+}
+
+// ChangeStatus 修改状态
+func (c *pluginsManage) ChangeStatus(r *ghttp.Request) {
+	var req *dao.PluginsManageStatusReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		c.FailJsonExit(r, err.(gvalid.Error).FirstString())
+	}
+	if err := service.PluginsManage.ChangeStatus(r.GetCtx(), req); err != nil {
+		c.FailJsonExit(r, err.Error())
+	} else {
+		c.SusJsonExit(r, "状态设置成功")
+	}
+}
+
+// Install 插件安装
+func (c *pluginsManage) Install(r *ghttp.Request) {
+	var req *dao.PluginsManageInstallReq
+	//获取参数
+	if err := r.Parse(&req); err != nil {
+		c.FailJsonExit(r, err.(gvalid.Error).FirstString())
+	}
+	if req.RToken == "" {
+		c.SusJsonExit(r, g.Map{
+			"code": -401,
+			"msg":  "请登录",
+		})
+	}
+	err := service.PluginsManage.Install(r.GetCtx(), req)
+	if err != nil {
+		c.FailJsonExit(r, err.Error())
+	}
+	c.SusJsonExit(r, "安装成功")
+}
+
+// Captcha 获取验证码
+func (c *pluginsManage) Captcha(r *ghttp.Request) {
+	idKeyC, base64stringC, err := service.PluginsManage.GetCaptcha(r.GetCtx())
+	if err != nil {
+		c.FailJsonExit(r, err.Error())
+	}
+	c.SusJsonExit(r, g.Map{
+		"base64stringC": base64stringC,
+		"idKeyC":        idKeyC,
+	})
+}
+
+// LoginR 登录插件服务
+func (c *pluginsManage) LoginR(r *ghttp.Request) {
+	var req *dao.PluginRLoginFormReq
+	// 通过Parse方法解析获取参数
+	err := r.Parse(&req)
+	if err != nil {
+		c.FailJsonExit(r, err.(gvalid.Error).FirstString())
+	}
+	var userInfo g.Map
+	userInfo, err = service.PluginsManage.LoginR(r.GetCtx(), req)
+	if err != nil {
+		c.FailJsonExit(r, err.Error())
+	}
+	c.SusJsonExit(r, userInfo)
+}
+
+// InstallOffLine 离线安装
+func (c *pluginsManage) InstallOffLine(r *ghttp.Request) {
+	upFile := r.GetUploadFile("file")
+	f, err := upFile.Open()
+	if err != nil {
+		c.FailJsonExit(r, err.Error())
+	}
+	defer f.Close()
+	d := make([]byte, upFile.Size)
+	_, err = f.Read(d)
+	if err != nil {
+		c.FailJsonExit(r, err.Error())
+	}
+	fileName := upFile.Filename
+	fileName = gstr.SubStr(fileName, gstr.PosR(fileName, "=")+1,
+		gstr.PosR(fileName, ".")-gstr.PosR(fileName, "=")-1)
+	err = service.PluginsManage.PluginIsExists(r.GetCtx(), fileName)
+	if err != nil {
+		c.FailJsonExit(r, err.Error())
+	}
+	err = service.PluginsManage.InstallFile(r.GetCtx(), d, fileName)
+	if err != nil {
+		c.FailJsonExit(r, err.Error())
+	}
+	c.SusJsonExit(r)
+}

+ 81 - 0
app/system/dao/internal/plugins_manage.go

@@ -0,0 +1,81 @@
+// ==========================================================================
+// GFast自动生成dao internal操作代码,无需手动修改,重新生成会自动覆盖.
+// 生成日期:2021-08-31 17:58:43
+// 生成路径: gfast/app/system/dao/internal/plugins_manage.go
+// 生成人:gfast
+// ==========================================================================
+
+package internal
+
+import (
+	"context"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+)
+
+// PluginsManageDao is the manager for logic model data accessing and custom defined data operations functions management.
+type PluginsManageDao struct {
+	Table   string               // Table is the underlying table name of the DAO.
+	Group   string               // Group is the database configuration group name of current DAO.
+	Columns PluginsManageColumns // Columns is the short type for Columns, which contains all the column names of Table for convenient usage.
+}
+
+// PluginsManageColumns defines and stores column names for table plugins_manage.
+type PluginsManageColumns struct {
+	Id            string // ID
+	StoreId       string // 插件在商城中的id
+	PName         string // 插件名称英文
+	PTitle        string // 插件名称
+	PDescription  string // 插件介绍
+	PAuth         string // 作者
+	IsInstall     string // 是否安装
+	Status        string // 状态
+	Version       string // 当前版本
+	Price         string // 价格
+	DownloadTimes string // 下载次数
+	InstallPath   string // 安装路径
+}
+
+var pluginsManageColumns = PluginsManageColumns{
+	Id:            "id",
+	StoreId:       "store_id",
+	PName:         "p_name",
+	PTitle:        "p_title",
+	PDescription:  "p_description",
+	PAuth:         "p_auth",
+	IsInstall:     "is_install",
+	Status:        "status",
+	Version:       "version",
+	Price:         "price",
+	DownloadTimes: "download_times",
+	InstallPath:   "install_path",
+}
+
+// NewPluginsManageDao creates and returns a new DAO object for table data access.
+func NewPluginsManageDao() *PluginsManageDao {
+	return &PluginsManageDao{
+		Group:   "default",
+		Table:   "plugins_manage",
+		Columns: pluginsManageColumns,
+	}
+}
+
+// DB retrieves and returns the underlying raw database management object of current DAO.
+func (dao *PluginsManageDao) DB() gdb.DB {
+	return g.DB(dao.Group)
+}
+
+// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
+func (dao *PluginsManageDao) Ctx(ctx context.Context) *gdb.Model {
+	return dao.DB().Model(dao.Table).Safe().Ctx(ctx)
+}
+
+// Transaction wraps the transaction logic using function f.
+// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
+// It commits the transaction and returns nil if function f returns nil.
+//
+// Note that, you should not Commit or Rollback the transaction in function f
+// as it is automatically handled by this function.
+func (dao *PluginsManageDao) Transaction(ctx context.Context, f func(ctx context.Context, tx *gdb.TX) error) (err error) {
+	return dao.Ctx(ctx).Transaction(ctx, f)
+}

+ 146 - 0
app/system/dao/plugins_manage.go

@@ -0,0 +1,146 @@
+// ==========================================================================
+// GFast自动生成dao操作代码,无需手动修改,重新生成不会自动覆盖.
+// 生成日期:2021-08-31 17:58:43
+// 生成路径: gfast/app/system/dao/plugins_manage.go
+// 生成人:gfast
+// ==========================================================================
+
+package dao
+
+import (
+	comModel "gfast/app/common/model"
+	"gfast/app/system/dao/internal"
+	"github.com/gogf/gf/os/gtime"
+)
+
+// pluginsManageDao is the manager for logic model data accessing and custom defined data operations functions management.
+// You can define custom methods on it to extend its functionality as you wish.
+type pluginsManageDao struct {
+	*internal.PluginsManageDao
+}
+
+var (
+	// PluginsManage is globally public accessible object for table tools_gen_table operations.
+	PluginsManage = pluginsManageDao{
+		internal.NewPluginsManageDao(),
+	}
+)
+
+// Fill with you ideas below.
+
+// PluginsManageSearchReq 分页请求参数
+type PluginsManageSearchReq struct {
+	PName  string `p:"pName"`  //插件名称英文
+	PTitle string `p:"pTitle"` //插件名称
+	PAuth  string `p:"pAuth"`  //作者
+	Status string `p:"status"` //状态
+	comModel.PageReq
+}
+
+// PluginsManageAddReq 添加操作请求参数
+type PluginsManageAddReq struct {
+	StoreId       int    `p:"storeId" `
+	PName         string `p:"pName" v:"required#插件名称英文不能为空"`
+	PTitle        string `p:"pTitle" `
+	PDescription  string `p:"pDescription" `
+	PAuth         string `p:"pAuth" `
+	Status        int    `p:"status" v:"required#状态不能为空"`
+	Version       string `p:"version" `
+	Price         uint   `p:"price" v:"required#价格不能为空"`
+	DownloadTimes uint   `p:"downloadTimes" v:"required#下载次数不能为空"`
+	IsInstall     int    `p:"isInstall" v:"required#是否安装不能为空"`
+}
+
+// PluginsManageEditReq 修改操作请求参数
+type PluginsManageEditReq struct {
+	Id            uint   `p:"id" v:"required#主键ID不能为空"`
+	StoreId       int    `p:"storeId" `
+	PName         string `p:"pName" v:"required#插件名称英文不能为空"`
+	PTitle        string `p:"pTitle" `
+	PDescription  string `p:"pDescription" `
+	PAuth         string `p:"pAuth" `
+	Status        int    `p:"status" v:"required#状态不能为空"`
+	Version       string `p:"version" `
+	Price         uint   `p:"price" v:"required#价格不能为空"`
+	DownloadTimes uint   `p:"downloadTimes" v:"required#下载次数不能为空"`
+	IsInstall     int    `p:"isInstall" v:"required#是否安装不能为空"`
+}
+
+// PluginsManageStatusReq 设置状态参数
+type PluginsManageStatusReq struct {
+	PluginId uint `p:"pluginId" v:"required#pluginId不能为空"`
+	Status   int  `p:"status" v:"required#状态不能为空"`
+}
+
+// PluginsManageListRes 列表返回结果
+type PluginsManageListRes struct {
+	Id            uint   `json:"id" `
+	PName         string `json:"pName" v:"required#插件名称英文不能为空"`
+	PTitle        string `json:"pTitle" `
+	PDescription  string `json:"pDescription" `
+	PAuth         string `json:"pAuth" `
+	Status        int    `json:"status" v:"required#状态不能为空"`
+	Version       string `json:"version" `
+	Price         uint   `json:"price" v:"required#价格不能为空"`
+	DownloadTimes uint   `json:"downloadTimes" v:"required#下载次数不能为空"`
+}
+
+// PluginsManageInfoRes 数据返回结果
+type PluginsManageInfoRes struct {
+	Id            uint   `json:"id" `
+	StoreId       int    `json:"storeId" `
+	PName         string `json:"pName" v:"required#插件名称英文不能为空"`
+	PTitle        string `json:"pTitle" `
+	PDescription  string `json:"pDescription" `
+	PAuth         string `json:"pAuth" `
+	IsInstall     int    `json:"isInstall" v:"required#是否安装不能为空"`
+	Status        int    `json:"status" v:"required#状态不能为空"`
+	Version       string `json:"version" `
+	Price         uint   `json:"price" v:"required#价格不能为空"`
+	DownloadTimes uint   `json:"downloadTimes" v:"required#下载次数不能为空"`
+}
+
+// CsPluginListRes 插件商城获取的插件数据
+type CsPluginListRes struct {
+	PluginId       uint        `orm:"plugin_id,primary" json:"pluginId"`     // ID
+	PluginCateId   uint        `orm:"plugin_cate_id" json:"pluginCateId"`    // 分类ID
+	PluginName     string      `orm:"plugin_name" json:"pluginName"`         // 插件名称
+	CodeName       string      `orm:"code_name" json:"CodeName"`             // 代码名称
+	PluginPrice    uint        `orm:"plugin_price" json:"pluginPrice"`       // 售价
+	PluginPriceStr string      `json:"pluginPriceStr"`                       // 售价decimal
+	PluginDiscount uint        `orm:"plugin_discount" json:"pluginDiscount"` // 折扣
+	PluginCreater  uint        `orm:"plugin_creater" json:"pluginCreater"`   // 开发人员ID
+	PublishDate    *gtime.Time `orm:"publish_date" json:"publishDate"`       // 发布日期
+	PluginThumb    string      `orm:"plugin_thumb" json:"pluginThumb"`       // 插件封面
+	PluginImgs     string      `orm:"plugin_imgs" json:"pluginImgs"`         // 插件预览图
+	CreatedBy      uint64      `orm:"created_by" json:"createdBy"`           // 创建人
+	CreatedAt      *gtime.Time `orm:"created_at" json:"createdAt"`           // 创建日期
+	UpdatedAt      *gtime.Time `orm:"updated_at" json:"updatedAt"`           // 修改日期
+	DeletedAt      *gtime.Time `orm:"deleted_at" json:"deletedAt"`           // 删除日期
+	DownloadTimes  uint64      `orm:"download_times" json:"downloadTimes"`   //下载次数
+	Description    string      `orm:"description" json:"description"`        //插件描述
+	PluginInfo     []*struct {
+		InfoId      uint   `json:"infoId"`      // ID
+		PluginId    uint   `json:"pluginId"`    // 插件ID
+		InfoVersion string `json:"infoVersion"` // 版本号
+	} `json:"pluginInfo"`
+	MemName   string `json:"memName" orm:"mem_name"`
+	Status    int    `json:"status"`
+	Version   string `json:"version" `
+	IsInstall int    `json:"isInstall"`
+}
+
+// PluginsManageInstallReq 插件安装操作参数
+type PluginsManageInstallReq struct {
+	PluginId uint   `p:"pluginId" v:"required#插件ID不能为空"`
+	Version  string `p:"version"`
+	RToken   string `p:"rToken"`
+}
+
+// PluginRLoginFormReq 远端登录请求参数
+type PluginRLoginFormReq struct {
+	Username   string `p:"username" v:"required#账号必须" json:"username"`
+	Password   string `p:"password" v:"required#密码不能为空" json:"password"`
+	VerifyCode string `p:"verifyCode" v:"required#验证码不能为空" json:"verifyCode"`
+	VerifyKey  string `p:"verifyKey" json:"verifyKey"`
+}

+ 24 - 0
app/system/model/plugins_manage.go

@@ -0,0 +1,24 @@
+// ==========================================================================
+// GFast自动生成model代码,无需手动修改,重新生成会自动覆盖.
+// 生成日期:2021-08-31 17:58:43
+// 生成路径: gfast/app/system/model/plugins_manage.go
+// 生成人:gfast
+// ==========================================================================
+
+package model
+
+// PluginsManage is the golang structure for table plugins_manage.
+type PluginsManage struct {
+	Id            uint   `orm:"id,primary" json:"id"`                // ID
+	StoreId       int    `orm:"store_id" json:"storeId"`             // 插件在商城中的id
+	PName         string `orm:"p_name" json:"pName"`                 // 插件名称英文
+	PTitle        string `orm:"p_title" json:"pTitle"`               // 插件名称
+	PDescription  string `orm:"p_description" json:"pDescription"`   // 插件介绍
+	PAuth         string `orm:"p_auth" json:"pAuth"`                 // 作者
+	IsInstall     int    `orm:"is_install" json:"isInstall"`         // 是否安装
+	Status        int    `orm:"status" json:"status"`                // 状态
+	Version       string `orm:"version" json:"version"`              // 当前版本
+	Price         uint   `orm:"price" json:"price"`                  // 价格
+	DownloadTimes uint   `orm:"download_times" json:"downloadTimes"` // 下载次数
+	InstallPath   string `orm:"install_oath" json:"installPath"`     // 安装路径
+}

+ 37 - 0
app/system/router/plugins_manage.go

@@ -0,0 +1,37 @@
+// ==========================================================================
+// GFast自动生成路由代码,只生成一次,按需修改,再次生成不会覆盖.
+// 生成日期:2021-08-31 17:58:43
+// 生成路径: gfast/app/system/router/plugins_manage.go
+// 生成人:gfast
+// ==========================================================================
+
+package router
+
+import (
+	"gfast/app/system/api"
+	"gfast/middleware"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+)
+
+//加载路由
+func init() {
+	s := g.Server()
+	s.Group("/", func(group *ghttp.RouterGroup) {
+		group.Group("/system", func(group *ghttp.RouterGroup) {
+			group.Group("/pluginsManage", func(group *ghttp.RouterGroup) {
+				//gToken拦截器
+				api.GfToken.AuthMiddleware(group)
+				//context拦截器
+				group.Middleware(middleware.Ctx, middleware.Auth)
+				group.GET("list", api.PluginsManage.List)
+				group.GET("get", api.PluginsManage.Get)
+				group.PUT("changeStatus", api.PluginsManage.ChangeStatus)
+				group.POST("install", api.PluginsManage.Install)
+				group.GET("captcha", api.PluginsManage.Captcha)
+				group.POST("loginR", api.PluginsManage.LoginR)
+				group.POST("installOffLine", api.PluginsManage.InstallOffLine)
+			})
+		})
+	})
+}

+ 450 - 0
app/system/service/plugins_manage.go

@@ -0,0 +1,450 @@
+// ==========================================================================
+// GFast自动生成业务逻辑层相关代码,只生成一次,按需修改,再次生成不会覆盖.
+// 生成日期:2021-08-31 17:58:43
+// 生成路径: gfast/app/system/service/plugins_manage.go
+// 生成人:gfast
+// ==========================================================================
+
+package service
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"gfast/app/common/global"
+	"gfast/app/system/dao"
+	"gfast/app/system/model"
+	"gfast/library"
+	"github.com/gogf/gf/container/garray"
+	"github.com/gogf/gf/container/gmap"
+	"github.com/gogf/gf/encoding/gcompress"
+	"github.com/gogf/gf/encoding/gjson"
+	"github.com/gogf/gf/encoding/gurl"
+	"github.com/gogf/gf/errors/gerror"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/os/gfile"
+	"github.com/gogf/gf/text/gstr"
+	"github.com/gogf/gf/util/gconv"
+)
+
+type pluginsManage struct {
+}
+
+var PluginsManage = new(pluginsManage)
+
+// GetList 获取列表
+func (s *pluginsManage) GetList(req *dao.PluginsManageSearchReq) (total, page int, list []*dao.CsPluginListRes, err error) {
+	//同步服务端插件商城
+	total, page, list, err = s.syncFromStore(req)
+	return
+}
+
+// GetInfoById 通过id获取
+func (s *pluginsManage) GetInfoById(ctx context.Context, id int64) (info *dao.PluginsManageInfoRes, err error) {
+	if id == 0 {
+		err = gerror.New("参数错误")
+		return
+	}
+	var data *model.PluginsManage
+	err = dao.PluginsManage.Ctx(ctx).Where(dao.PluginsManage.Columns.Id, id).Scan(&data)
+	if err != nil {
+		g.Log().Error(err)
+	}
+	if data == nil || err != nil {
+		err = gerror.New("获取信息失败")
+	}
+	info = &dao.PluginsManageInfoRes{
+		Id:            data.Id,
+		StoreId:       data.StoreId,
+		PName:         data.PName,
+		PTitle:        data.PTitle,
+		PDescription:  data.PDescription,
+		PAuth:         data.PAuth,
+		IsInstall:     data.IsInstall,
+		Status:        data.Status,
+		Version:       data.Version,
+		Price:         data.Price,
+		DownloadTimes: data.DownloadTimes,
+	}
+	return
+}
+
+// Add 添加
+func (s *pluginsManage) Add(ctx context.Context, req *dao.PluginsManageAddReq) (err error) {
+	_, err = dao.PluginsManage.Ctx(ctx).Insert(req)
+	return
+}
+
+// Edit 修改
+func (s *pluginsManage) Edit(ctx context.Context, req *dao.PluginsManageEditReq) error {
+	_, err := dao.PluginsManage.Ctx(ctx).FieldsEx(dao.PluginsManage.Columns.Id).Where(dao.PluginsManage.Columns.Id, req.Id).
+		Update(req)
+	return err
+}
+
+// DeleteByIds 删除
+func (s *pluginsManage) DeleteByIds(ctx context.Context, ids []int) (err error) {
+	if len(ids) == 0 {
+		err = gerror.New("参数错误")
+		return
+	}
+	_, err = dao.PluginsManage.Ctx(ctx).Delete(dao.PluginsManage.Columns.Id+" in (?)", ids)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("删除失败")
+	}
+	return
+}
+
+// ChangeStatus 修改状态
+func (s *pluginsManage) ChangeStatus(ctx context.Context, req *dao.PluginsManageStatusReq) error {
+	_, err := dao.PluginsManage.Ctx(ctx).Where(dao.PluginsManage.Columns.StoreId, req.PluginId).Update(g.Map{
+		dao.PluginsManage.Columns.Status: req.Status,
+	})
+	return err
+}
+
+//同步插件商城中的插件
+func (s *pluginsManage) syncFromStore(req *dao.PluginsManageSearchReq) (total, page int, csPluginList []*dao.CsPluginListRes, err error) {
+	storeUrl := g.Cfg().GetString("plugin.serverUrl") + "/codeStore/pluginList"
+	res := (*ghttp.ClientResponse)(nil)
+	if req.PageNum == 0 {
+		req.PageNum = 1
+	}
+	page = req.PageNum
+	res, err = g.Client().Ctx(req.Ctx).Get(storeUrl, g.MapStrAny{
+		"pageNum":    req.PageNum,
+		"PageSize":   req.PageSize,
+		"pluginName": req.PTitle,
+	})
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取插件数据失败")
+		return
+	}
+	defer res.Close()
+	var data map[string]interface{}
+	b := res.ReadAll()
+	err = json.Unmarshal(b, &data)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取插件数据失败")
+		return
+	}
+	if gconv.Int(data["code"]) == 0 {
+		err = gconv.Structs((data["data"].(g.Map))["list"], &csPluginList)
+		if err != nil {
+			return
+		}
+		csPluginList, err = s.updatePlugins(req.Ctx, csPluginList)
+		if err != nil {
+			return
+		}
+		total = gconv.Int((data["data"].(g.Map))["total"])
+	} else {
+		err = gerror.New(data["msg"].(string))
+		return
+	}
+	return
+}
+
+// 更新插件数据
+func (s *pluginsManage) updatePlugins(ctx context.Context, csList []*dao.CsPluginListRes) (newList []*dao.CsPluginListRes, err error) {
+	ids := make([]uint, len(csList))
+	for k, v := range csList {
+		ids[k] = v.PluginId
+	}
+	//查询插件信息
+	var pluginList []*model.PluginsManage
+	err = dao.PluginsManage.Ctx(ctx).Where(dao.PluginsManage.Columns.StoreId+" in(?)", ids).Scan(&pluginList)
+	if err != nil {
+		return
+	}
+	hasIds := garray.NewArraySize(len(pluginList), 100)
+	gmp := gmap.New()
+	for k, v := range pluginList {
+		hasIds.Set(k, v.StoreId)
+		gmp.Set(v.StoreId, v)
+	}
+	for _, v := range csList {
+		pluginId := gconv.Int(v.PluginId)
+		if hasIds.Len() > 0 && hasIds.Contains(pluginId) {
+			plugin := gmp.Get(pluginId).(*model.PluginsManage)
+			//修改
+			version := plugin.Version
+			if plugin.IsInstall == 0 && len(v.PluginInfo) > 0 {
+				version = v.PluginInfo[0].InfoVersion
+			}
+			err = s.Edit(ctx, &dao.PluginsManageEditReq{
+				Id:            plugin.Id,
+				StoreId:       pluginId,
+				PName:         v.CodeName,
+				PTitle:        v.PluginName,
+				PDescription:  v.Description,
+				PAuth:         v.MemName,
+				Status:        plugin.Status,
+				Version:       version,
+				Price:         v.PluginPrice,
+				DownloadTimes: gconv.Uint(v.DownloadTimes),
+				IsInstall:     plugin.IsInstall,
+			})
+			v.Status = plugin.Status
+			v.Version = version
+			v.IsInstall = plugin.IsInstall
+			v.PluginPriceStr = s.Int64ToDecimal(gconv.Int64(v.PluginPrice))
+		} else {
+			//新增
+			version := ""
+			if len(v.PluginInfo) > 0 {
+				version = v.PluginInfo[0].InfoVersion
+			}
+			err = s.Add(ctx, &dao.PluginsManageAddReq{
+				StoreId:       pluginId,
+				PName:         v.CodeName,
+				PTitle:        v.PluginName,
+				PDescription:  v.Description,
+				PAuth:         v.MemName,
+				Status:        0,
+				Version:       version,
+				Price:         v.PluginPrice,
+				DownloadTimes: gconv.Uint(v.DownloadTimes),
+				IsInstall:     0,
+			})
+			v.Status = 0
+			v.Version = version
+			v.IsInstall = 0
+			v.PluginPriceStr = s.Int64ToDecimal(gconv.Int64(v.PluginPrice))
+		}
+		if err != nil {
+			return
+		}
+	}
+	newList = csList
+	return
+}
+
+// DecimalToInt64 元转分
+func (s *pluginsManage) DecimalToInt64(decimal string) (i int64) {
+	pos := gstr.PosR(decimal, ".")
+	integer := gconv.Int64(gstr.SubStr(decimal, 0, pos)) * 100
+	dec := int64(0)
+	if pos > -1 {
+		dec = gconv.Int64(gstr.SubStr(decimal, pos+1, 2))
+	}
+	i = integer + dec
+	return
+}
+
+// Int64ToDecimal 分转元
+func (s *pluginsManage) Int64ToDecimal(i int64) (decimal string) {
+	b := []byte(gconv.String(i))
+	for {
+		if len(b) >= 2 {
+			break
+		}
+		b = append([]byte{'0'}, b...)
+	}
+	integer := b[:len(b)-2]
+	if len(integer) == 0 {
+		integer = []byte{'0'}
+	}
+	dec := b[len(b)-2:]
+	decimal = fmt.Sprintf("%s.%s", integer, dec)
+	return
+}
+
+// Install 插件安装
+func (s *pluginsManage) Install(ctx context.Context, req *dao.PluginsManageInstallReq) (err error) {
+	//生成下载链接
+	storeUrl := g.Cfg().GetString("plugin.serverUrl") + "/codeStoreFrontAdmin/getDownloadInfo"
+	res := (*ghttp.ClientResponse)(nil)
+	res, err = g.Client().Ctx(ctx).Get(fmt.Sprintf("%s?pluginId=%d&version=%s&token=%s", storeUrl, req.PluginId, req.Version,
+		gurl.RawEncode(req.RToken)))
+	if err != nil {
+		return
+	}
+	defer res.Close()
+	var data map[string]interface{}
+	b := res.ReadAll()
+	err = json.Unmarshal(b, &data)
+	if err != nil {
+		return
+	}
+	if gconv.Int(data["code"]) == 0 {
+		url := fmt.Sprintf("%s/%s&token=%s", g.Cfg().GetString("plugin.serverUrl"),
+			(data["data"]).(string), gurl.RawEncode(req.RToken))
+		//下载插件并安装
+		err = s.downloadAndInstall(ctx, url)
+	} else {
+		err = gerror.New(data["msg"].(string))
+	}
+	return
+}
+
+// GetCaptcha 获取验证码
+func (s *pluginsManage) GetCaptcha(ctx context.Context) (idKeyC, base64stringC string, err error) {
+	storeUrl := g.Cfg().GetString("plugin.serverUrl") + "/captcha/get"
+	res := (*ghttp.ClientResponse)(nil)
+	res, err = g.Client().Ctx(ctx).Get(storeUrl)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取验证码失败")
+		return
+	}
+	defer res.Close()
+	var data map[string]interface{}
+	b := res.ReadAll()
+	err = json.Unmarshal(b, &data)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取插件数据失败")
+		return
+	}
+	if gconv.Int(data["code"]) == 0 {
+		data = (data["data"]).(g.Map)
+		idKeyC = gconv.String(data["idKeyC"])
+		base64stringC = gconv.String(data["base64stringC"])
+	} else {
+		err = gerror.New(data["msg"].(string))
+	}
+	return
+}
+
+// LoginR 登录
+func (s *pluginsManage) LoginR(ctx context.Context, loginReq *dao.PluginRLoginFormReq) (userInfo g.Map, err error) {
+	storeUrl := g.Cfg().GetString("plugin.serverUrl") + "/codeStoreFrontAdmin/login"
+	res := (*ghttp.ClientResponse)(nil)
+	res, err = g.Client().Ctx(ctx).Post(storeUrl, loginReq)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取验证码失败")
+		return
+	}
+	defer res.Close()
+	var data map[string]interface{}
+	b := res.ReadAll()
+	err = json.Unmarshal(b, &data)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("登录失败")
+		return
+	}
+	if gconv.Int(data["code"]) == 0 {
+		userInfo = (data["data"]).(g.Map)
+	} else {
+		err = gerror.New(data["msg"].(string))
+	}
+	return
+}
+
+// 下载并安装插件
+func (s *pluginsManage) downloadAndInstall(ctx context.Context, url string) error {
+	res, err := g.Client().Ctx(ctx).Get(url)
+	if err != nil {
+		return err
+	}
+	defer res.Close()
+	ct := res.Header.Get("Content-Type")
+	if gstr.ContainsI(ct, "json") {
+		var data map[string]interface{}
+		b := res.ReadAll()
+		err = json.Unmarshal(b, &data)
+		if err != nil {
+			return err
+		}
+		if gconv.Int(data["code"]) != 0 {
+			err = gerror.New(data["msg"].(string))
+		}
+		return err
+	} else {
+		//安装
+		//获取插件名称
+		fileName := res.Header.Get("content-disposition")
+		fileName = gstr.SubStr(fileName, gstr.PosR(fileName, "=")+1,
+			gstr.PosR(fileName, ".")-gstr.PosR(fileName, "=")-1)
+		err = s.InstallFile(ctx, res.ReadAll(), fileName)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// InstallFile 安装插件文件
+func (s *pluginsManage) InstallFile(ctx context.Context, data []byte, fileName string) (err error) {
+	//获取插件下载路径
+	downloadPath := library.GetExcPath() + "/data/installPlugins"
+	if !gfile.IsDir(downloadPath) {
+		err = gfile.Mkdir(downloadPath)
+		if err != nil {
+			return
+		}
+	}
+	g.Log().Debug(downloadPath, fileName)
+	// 删除安装临时文件
+	defer gfile.Remove(downloadPath + "/" + fileName)
+	err = gcompress.UnZipContent(data, downloadPath)
+	if err != nil {
+		return
+	}
+	//获取插件配置信息
+	var installCfg *gjson.Json
+	installCfg, err = gjson.Load(downloadPath + "/" + fileName + "/install.json")
+	if err != nil {
+		return
+	}
+	mustGfastVersion := installCfg.GetString("minGfastVersion")
+	if gstr.CompareVersion(mustGfastVersion, global.Version) > 0 {
+		err = gerror.New(fmt.Sprintf("您的gfast版本过低,此插件要求gfast版本为:%s", mustGfastVersion))
+		return
+	}
+	//获取本项目安装情况
+	var plugin *model.PluginsManage
+	err = dao.PluginsManage.Ctx(ctx).Where(dao.PluginsManage.Columns.PName, fileName).Limit(1).Scan(&plugin)
+	if err != nil {
+		return
+	}
+	if plugin == nil {
+		err = gerror.New("插件信息不存在,请刷新页面后再安装。")
+		return
+	}
+	//复制插件文件到对应目录
+	//1.后端
+	err = gfile.Copy(downloadPath+"/"+fileName+"/go/", library.GetExcPath())
+	if err != nil {
+		return
+	}
+	//2.前端
+	fontRoot := g.Cfg().GetString("gen.frontDir")
+	if !gfile.IsDir(fontRoot) {
+		err = gerror.New("前端路径不存在,请配置gen.frontDir")
+		return
+	}
+	err = gfile.Copy(downloadPath+"/"+fileName+"/vue/", fontRoot+"/src")
+	if err != nil {
+		return
+	}
+	// 安装成功后修改插件安装状态及安装路径
+	_, err = dao.PluginsManage.Ctx(ctx).WherePri(plugin.Id).Update(g.Map{
+		dao.PluginsManage.Columns.IsInstall:   1,
+		dao.PluginsManage.Columns.Status:      1,
+		dao.PluginsManage.Columns.InstallPath: installCfg.GetString("installPath"),
+		dao.PluginsManage.Columns.Version:     installCfg.GetString("version"),
+	})
+	return
+}
+
+// PluginIsExists 判断插件是否存在
+func (s *pluginsManage) PluginIsExists(ctx context.Context, name string) error {
+	info := (*model.PluginsManage)(nil)
+	err := dao.PluginsManage.Ctx(ctx).Where(dao.PluginsManage.Columns.PName, name).Limit(1).
+		Fields(dao.PluginsManage.Columns.Id).Scan(&info)
+	if err != nil {
+		return err
+	}
+	if info == nil {
+		return gerror.New("不属于官方插件,无法安装。")
+	}
+	return nil
+}

+ 7 - 2
config/config.toml

@@ -46,7 +46,7 @@
 # Redis数据库配置
 [redis]
     open = true #是否开启 redis 缓存 若不开启使用gchache缓存方式
-    default = "127.0.0.1:6379,9?idleTimeout=20&maxActive=100"
+    default = "127.0.0.1:6379,2?idleTimeout=20&maxActive=100"
 
 #jwt配置
 [gToken]
@@ -87,4 +87,9 @@
         SecretID =  "填写您的SecretID"
         SecretKey = "填写您的SecretKey"
     [upload.local] #本地上传配置
-        UpPath = "/pub_upload/" #上传路径
+        UpPath = "/pub_upload/" #上传路径
+
+
+# 插件管理
+[plugin]
+    serverUrl = "http://localhost:8199"  #获取插件商城插件列表

+ 11 - 31
library/response.go

@@ -1,11 +1,10 @@
 package library
 
 import (
-	"github.com/gogf/gf/frame/g"
+	"fmt"
+	"github.com/gogf/gf/encoding/gurl"
 	"github.com/gogf/gf/net/ghttp"
 	"github.com/gogf/gf/os/gview"
-	"github.com/gogf/gf/text/gstr"
-	"github.com/gogf/gf/util/gconv"
 )
 
 const (
@@ -87,35 +86,16 @@ func (res *Response) FailJson(isExit bool, r *ghttp.Request, msg string, data ..
 
 //模板输出
 func (res *Response) WriteTpl(r *ghttp.Request, tpl string, view *gview.View, params ...gview.Params) error {
-	//绑定模板中需要用到的方法
-	view.BindFuncMap(g.Map{
-		// 根据长度i来切割字符串
-		"subStr": func(str interface{}, i int) (s string) {
-			s1 := gconv.String(str)
-			if gstr.LenRune(s1) > i {
-				s = gstr.SubStrRune(s1, 0, i) + "..."
-				return s
-			}
-			return s1
-		},
-		// 格式化时间戳 年月日
-		"timeFormatYear": func(time interface{}) string {
-			return TimeStampToDate(gconv.Int64(time))
-		},
-		// 格式化时间戳 年月日时分秒
-		"timeFormatDateTime": func(time interface{}) string {
-			return TimeStampToDateTime(gconv.Int64(time))
-		},
-		"add": func(a, b interface{}) int {
-			return gconv.Int(a) + gconv.Int(b)
-		},
-	})
 	//设置全局变量
-	domain, _ := GetDomain(r)
-	view.Assigns(g.Map{
-		"domain": domain,
-	})
-	return r.Response.WriteTpl(tpl, params...)
+	urlInfo, _ := gurl.ParseURL(r.GetUrl(), -1)
+	view.Assign("urlInfo", urlInfo)
+	r.SetView(view)
+	err := r.Response.WriteTpl(tpl, params...)
+	if err != nil {
+		fmt.Println(err.Error())
+		r.Response.WriteExit(err.Error())
+	}
+	return nil
 }
 
 func (res *Response) Redirect(r *ghttp.Request, location string, code ...int) {

+ 13 - 0
library/utils.go

@@ -14,6 +14,9 @@ import (
 	"github.com/gogf/gf/text/gstr"
 	"github.com/gogf/gf/util/gconv"
 	"net"
+	"os"
+	"os/exec"
+	"path/filepath"
 	"strings"
 )
 
@@ -247,3 +250,13 @@ func CurrencyLong(currency interface{}) int64 {
 	}
 	return 0
 }
+
+func GetExcPath() string {
+	file, _ := exec.LookPath(os.Args[0])
+	// 获取包含可执行文件名称的路径
+	path, _ := filepath.Abs(file)
+	// 获取可执行文件所在目录
+	index := strings.LastIndex(path, string(os.PathSeparator))
+	ret := path[:index]
+	return strings.Replace(ret, "\\", "/", -1)
+}

+ 2 - 2
template/vm/vue/list-vue.template

@@ -277,11 +277,11 @@
        </el-form-item>
        {{else if eq $column.HtmlType "file"}}
         <el-form-item label="{{$column.ColumnComment}}" prop="{{$column.HtmlField}}" >
-        <up-file :action="apiUrl+'/system/upload/upFile'" v-model="form.{{$column.HtmlField}}" @setUpFielList="setUpFileList{{$column.GoField}}" :limit="1"></up-file>
+        <up-file :action="apiUrl+'/system/upload/upFile'" v-model="form.{{$column.HtmlField}}" @set-up-file-list="setUpFileList{{$column.GoField}}" :limit="1"></up-file>
         </el-form-item>
         {{else if eq $column.HtmlType "files"}}
         <el-form-item label="{{$column.ColumnComment}}" prop="{{$column.HtmlField}}" >
-        <up-file :action="apiUrl+'/system/upload/upFile'" v-model="form.{{$column.HtmlField}}" @setUpFielList="setUpFileList{{$column.GoField}}" :limit="10"></up-file>
+        <up-file :action="apiUrl+'/system/upload/upFile'" v-model="form.{{$column.HtmlField}}" @set-up-file-list="setUpFileList{{$column.GoField}}" :limit="10"></up-file>
         </el-form-item>
        {{end}}
        {{end}}

+ 2 - 2
template/vm/vue/tree-vue.template

@@ -281,11 +281,11 @@
        </el-form-item>
        {{else if eq $column.HtmlType "file"}}
         <el-form-item label="{{$column.ColumnComment}}" prop="{{$column.HtmlField}}" >
-        <up-file :action="apiUrl+'/system/upload/upFile'" v-model="form.{{$column.HtmlField}}" @setUpFielList="setUpFileList{{$column.GoField}}" :limit="1"></up-file>
+        <up-file :action="apiUrl+'/system/upload/upFile'" v-model="form.{{$column.HtmlField}}" @set-up-file-list="setUpFileList{{$column.GoField}}" :limit="1"></up-file>
         </el-form-item>
         {{else if eq $column.HtmlType "files"}}
         <el-form-item label="{{$column.ColumnComment}}" prop="{{$column.HtmlField}}" >
-        <up-file :action="apiUrl+'/system/upload/upFile'" v-model="form.{{$column.HtmlField}}" @setUpFielList="setUpFileList{{$column.GoField}}" :limit="10"></up-file>
+        <up-file :action="apiUrl+'/system/upload/upFile'" v-model="form.{{$column.HtmlField}}" @set-up-file-list="setUpFileList{{$column.GoField}}" :limit="10"></up-file>
         </el-form-item>
        {{end}}
        {{end}}