Parcourir la source

细节修复及功能完善

yxh il y a 4 ans
Parent
commit
265350a3a9

+ 201 - 0
LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 8 - 2
README.MD

@@ -111,10 +111,11 @@ gitee地址:[https://gitee.com/tiger1103/gfast-ui](https://gitee.com/tiger1103
 
 
 ### 生成dao
-因为我们在开发过程中,goFrame框架的gf-cli 一直在更新功能,建议不要直接去覆盖,生成到tmp目录后将需要的文件复制到对应的地方
+因为我们在开发过程中,goFrame框架的gf-cli 一直在更新功能,如果是修改了表重新生成建议不要直接去覆盖,生成到tmp目录后将需要的文件复制到对应的地方
 ```
 例如: gf gen dao -path ./tmp  -l "mysql:root:123456@tcp(127.0.0.1:3306)/gfast-v2" -t 表名
 ```
+*** 注意:使用gf-cli 版本最好 >= v1.16.4 ***
 
 ## 交流QQ群
 
@@ -127,4 +128,9 @@ gitee地址:[https://gitee.com/tiger1103/gfast-ui](https://gitee.com/tiger1103
 
 > 2、用户不得利用Gfast从事非法行为,用户应当合法合规的使用,发现用户在使用产品时有任何的非法行为,Gfast有权配合有关机关进行调查或向政府部门举报,Gfast不承担用户因非法行为造成的任何法律责任,一切法律责任由用户自行承担,如因用户使用造成第三方损害的,用户应当依法予以赔偿。
 
-> 3、所有与使用Gfast相关的资源直接风险均由用户承担。
+> 3、所有与使用Gfast相关的资源直接风险均由用户承担。
+
+
+## 商用说明
+> 商用注意事项
+如果您将此项目用于商业用途,请遵守Apache2.0协议并保留作者技术支持声明。

+ 26 - 13
app/common/adapter/upload.go

@@ -31,19 +31,32 @@ type upload struct {
 	adapter UploadAdapter
 }
 
-var Upload = &upload{
-	//使用本地上传
-	adapter: UploadLocalAdapter{
-		UpPath:     "/pub_upload/",
-		UploadPath: g.Cfg().GetString("server.ServerRoot") + "/pub_upload/",
-	},
-	//使用腾讯云COS上传
-	/*adapter: UploadTencentCOSAdapter{
-		UpPath:    "/gfast/",
-		RawUrl:    "https://您的cos空间域名.cos.ap-chongqing.myqcloud.com",
-		SecretID:  "填写您的SecretID",
-		SecretKey: "填写您的SecretKey",
-	},*/
+var (
+	upType = g.Cfg().GetString("upload.type")
+	Upload *upload
+)
+
+func init() {
+	var adp UploadAdapter
+	switch upType {
+	case "local":
+		//使用本地上传
+		adp = UploadLocalAdapter{
+			UpPath:     "/pub_upload/",
+			UploadPath: g.Cfg().GetString("server.ServerRoot") + "/pub_upload/",
+		}
+	case "tencentCOS":
+		//使用腾讯云COS上传
+		adp = UploadTencentCOSAdapter{
+			UpPath:    g.Cfg().GetString("upload.tencentCOS.UpPath"),
+			RawUrl:    g.Cfg().GetString("upload.tencentCOS.RawUrl"),
+			SecretID:  g.Cfg().GetString("upload.tencentCOS.SecretID"),
+			SecretKey: g.Cfg().GetString("upload.tencentCOS.SecretKey"),
+		}
+	}
+	Upload = &upload{
+		adapter: adp,
+	}
 }
 
 func (u upload) UpImg(file *ghttp.UploadFile) (fileInfo *FileInfo, err error) {

+ 1 - 0
app/common/model/req.go

@@ -10,4 +10,5 @@ type PageReq struct {
 	PageNum   int    `p:"PageNum"`   //当前页码
 	PageSize  int    `p:"pageSize"`  //每页数
 	Ctx       context.Context
+	OrderBy   string //排序方式
 }

+ 3 - 0
app/system/api/sys_base.go

@@ -14,5 +14,8 @@ type systemBase struct {
 // GetCurrentUser 获取当前登陆用户信息
 func (c *systemBase) GetCurrentUser(ctx context.Context) *dao.CtxUser {
 	context := service.Context.Get(ctx)
+	if context == nil {
+		return nil
+	}
 	return context.User
 }

+ 10 - 0
app/system/api/sys_dict_type.go

@@ -94,3 +94,13 @@ func (c *dictType) Delete(r *ghttp.Request) {
 	comService.Cache.New().RemoveByTag(global.SysDictTag)
 	c.SusJsonExit(r)
 }
+
+// OptionSelect 获取字典选择框列表
+func (c *dictType) OptionSelect(r *ghttp.Request) {
+	//获取所有字典类型列表
+	list, err := service.SysDictType.GetAllDictType()
+	if err != nil {
+		c.FailJsonExit(r, err.Error())
+	}
+	c.SusJsonExit(r, list)
+}

+ 3 - 3
app/system/api/sys_oper_log.go

@@ -76,8 +76,8 @@ func (c *sysOperLog) Clear(r *ghttp.Request) {
 
 // OperationLog 操作日志记录
 func (c *sysOperLog) OperationLog(r *ghttp.Request) {
-	user := c.GetCurrentUser(r.GetCtx())
-	if user == nil {
+	userInfo := c.GetCurrentUser(r.GetCtx())
+	if userInfo == nil {
 		return
 	}
 	url := r.Request.URL //请求地址
@@ -98,7 +98,7 @@ func (c *sysOperLog) OperationLog(r *ghttp.Request) {
 	}
 
 	data := &dao.SysOperLogAdd{
-		User:         user,
+		User:         userInfo,
 		Menu:         menu,
 		Url:          url,
 		Params:       r.GetMap(),

+ 1 - 1
app/system/dao/context.go

@@ -36,7 +36,7 @@ func (ctxUser *CtxUser) GetUserId() (id uint64) {
 
 // GetDept 获取登录用户所属部门
 func (ctxUser *CtxUser) GetDept() (err error, dept *model.SysDept) {
-	err = g.DB().Model(SysDept.Table).WherePri(ctxUser.DeptId).Scan(&dept)
+	err = g.DB().Model(SysDept.Table).Fields(SysDept.C.DeptId, SysDept.C.DeptName).WherePri(ctxUser.DeptId).Scan(&dept)
 	if dept == nil {
 		dept = &model.SysDept{}
 	}

+ 1 - 0
app/system/router/router.go

@@ -52,6 +52,7 @@ func init() {
 				group.GET("/dict/type/one", api.DictType.Get)
 				group.PUT("/dict/type/edit", api.DictType.Edit)
 				group.DELETE("/dict/type/delete", api.DictType.Delete)
+				group.GET("/dict/type/optionSelect", api.DictType.OptionSelect)
 				//字典数据
 				group.GET("/dict/data/GetDictData", api.DictData.GetDictData)
 				group.GET("/dict/data/list", api.DictData.List)

+ 22 - 0
app/system/service/sys_dict_type.go

@@ -2,7 +2,9 @@ package service
 
 import (
 	"context"
+	"gfast/app/common/global"
 	comModel "gfast/app/common/model"
+	comService "gfast/app/common/service"
 	"gfast/app/system/dao"
 	"gfast/app/system/model"
 	"github.com/gogf/gf/container/garray"
@@ -154,3 +156,23 @@ func (s *sysDictType) GetDictById(id int) (dict *model.SysDictType, err error) {
 	}
 	return
 }
+
+// GetAllDictType 获取所有正常状态下的字典类型
+func (s *sysDictType) GetAllDictType() (list []*model.SysDictType, err error) {
+	cache := comService.Cache.New()
+	//从缓存获取
+	data := cache.Get(global.SysDict + "_dict_type_all")
+	if data != nil {
+		err = gconv.Structs(data, &list)
+		return
+	}
+	err = dao.SysDictType.Where("status", 1).Order("dict_id ASC").Scan(&list)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("获取字典类型数据出错")
+		return
+	}
+	//缓存
+	cache.Set(global.SysDict+"_dict_type_all", list, 0, global.SysDictTag)
+	return
+}

+ 1 - 1
app/system/service/sys_user.go

@@ -420,7 +420,7 @@ func (s *sysUser) GetUserList(req *model.SysUserSearchReq) (total, page int, use
 		userModel = userModel.Where("created_at >=?", req.BeginTime)
 	}
 	if req.EndTime != "" {
-		userModel = userModel.Where("create_time <=?", req.EndTime)
+		userModel = userModel.Where("created_at <=?", req.EndTime)
 	}
 	total, err = userModel.Count()
 	if err != nil {

+ 26 - 6
config/config.toml

@@ -24,9 +24,9 @@
 
 # Template.
 [viewer]
-    Path        = "template"
-    DefaultFile = "index.html"
-    Delimiters  =  ["${", "}"]
+    paths       = ["./template"]
+    defaultFile = "index.html"
+    delimiters  =  ["${", "}"]
 
 # Database.
 [database]
@@ -46,14 +46,14 @@
 # Redis数据库配置
 [redis]
     open = true #是否开启 redis 缓存 若不开启使用gchache缓存方式
-    default = "127.0.0.1:6379,9?idleTimeout=600"
+    default = "127.0.0.1:6379,9?idleTimeout=20&maxActive=100"
 
 #jwt配置
 [gToken]
     [gToken.system]
         CacheMode = 2
         CacheKey = "GToken:"
-        Timeout = 3600000 #1个小时
+        Timeout = 10800000 #3个小时
         MaxRefresh = 0
         TokenDelimiter="_"
         EncryptKey = "koi29a83idakguqjq29asd9asd8a7jhq"
@@ -65,4 +65,24 @@
 #casbin配置
 [casbin]
     modelFile="./config/casbin_conf/rbac_model.conf"
-    policyFile="./config/casbin_conf/rbac_policy.csv"
+    policyFile="./config/casbin_conf/rbac_policy.csv"
+
+
+# Gen
+[gen]
+    author        = "gfast"
+    moduleName    = "system"
+    packageName   = "gfast/app/system"
+    autoRemovePre = true   #是否自动删除表前缀
+    tablePrefix   = "table_,qxkj_"   #表前缀
+    templatePath  = "./template/vm"  #代码生成模板路径
+    fontDir = "F:/tools/webProject/p2021/gfast-v2-ui/ruoyi-ui" #前端路径
+
+# 上传配置
+[upload]
+    type = "local"  #local:本地,tencentCOS:腾讯云 , 七牛云 阿里云等开发中...
+    [upload.tencentCOS] #腾讯云cos配置
+        UpPath =    "/gfast/"
+        RawUrl =    "https://您的cos空间域名.cos.ap-chongqing.myqcloud.com"
+        SecretID =  "填写您的SecretID"
+        SecretKey = "填写您的SecretKey"

+ 248 - 0
library/slice_tree.go

@@ -0,0 +1,248 @@
+package library
+
+import (
+	"fmt"
+	"github.com/gogf/gf/container/garray"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/util/gconv"
+	"reflect"
+)
+
+//有层级关系的数组,父级-》子级 排序
+func ParentSonSort(list g.List, params ...interface{}) g.List {
+	args := make([]interface{}, 8)
+	for k, v := range params {
+		if k == 8 {
+			break
+		}
+		args[k] = v
+	}
+	var (
+		pid       int    //父级id
+		level     int    //层级数
+		fieldName string //父级id键名
+		id        string //id键名
+		levelName string //层级名称
+		title     string //标题名称
+		breaks    int    //中断层级
+		prefixStr string //字符串前缀
+	)
+	pid = gconv.Int(GetSliceByKey(args, 0, 0))
+	level = gconv.Int(GetSliceByKey(args, 1, 0))
+	fieldName = gconv.String(GetSliceByKey(args, 2, "pid"))
+	id = gconv.String(GetSliceByKey(args, 3, "id"))
+	levelName = gconv.String(GetSliceByKey(args, 4, "flg"))
+	title = gconv.String(GetSliceByKey(args, 5, "title"))
+	breaks = gconv.Int(GetSliceByKey(args, 6, -1))
+	prefixStr = gconv.String(GetSliceByKey(args, 7, "─"))
+	//定义一个新slice用于返回
+	var returnSlice g.List
+	for _, v := range list {
+		if pid == gconv.Int(v[fieldName]) {
+			v[levelName] = level
+			levelClone := level
+			titlePrefix := ""
+			for {
+				if levelClone < 0 {
+					break
+				}
+				titlePrefix += prefixStr
+				levelClone--
+			}
+			titlePrefix = "├" + titlePrefix
+			if level == 0 {
+				v["title_prefix"] = ""
+			} else {
+				v["title_prefix"] = titlePrefix
+			}
+			v["title_show"] = fmt.Sprintf("%s%s", v["title_prefix"], v[title])
+			returnSlice = append(returnSlice, v)
+			if breaks != -1 && breaks == level {
+				continue
+			}
+			args[0] = v[id]
+			args[1] = level + 1
+			newSlice2 := ParentSonSort(list, args...)
+			if len(newSlice2) > 0 {
+				returnSlice = append(returnSlice, newSlice2...)
+			}
+		}
+	}
+	return returnSlice
+}
+
+//有层级关系的数组 ,将子级压入到父级(树形结构)
+func PushSonToParent(list g.List, params ...interface{}) g.List {
+	args := make([]interface{}, 7)
+	for k, v := range params {
+		if k == 7 {
+			break
+		}
+		args[k] = v
+	}
+	var (
+		pid         string      //父级id
+		fieldName   string      //父级id键名
+		id          string      //id键名
+		key         string      //子级数组键名
+		filter      string      //过滤键名
+		filterVal   interface{} //过滤的值
+		showNoChild bool        //是否显示不存在的子级健
+	)
+	pid = gconv.String(GetSliceByKey(args, 0, 0))
+	fieldName = gconv.String(GetSliceByKey(args, 1, "pid"))
+	id = gconv.String(GetSliceByKey(args, 2, "id"))
+	key = gconv.String(GetSliceByKey(args, 3, "children"))
+	filter = gconv.String(GetSliceByKey(args, 4, ""))
+	filterVal = GetSliceByKey(args, 5, nil)
+	showNoChild = gconv.Bool(GetSliceByKey(args, 6, true))
+	var returnList g.List
+	for _, v := range list {
+		if gconv.String(v[fieldName]) == pid {
+			if filter != "" {
+				if reflect.DeepEqual(v[filter], filterVal) {
+					args[0] = v[id]
+					child := PushSonToParent(list, args...)
+					if child != nil || showNoChild {
+						v[key] = child
+					}
+					returnList = append(returnList, v)
+				}
+			} else {
+				args[0] = v[id]
+				child := PushSonToParent(list, args...)
+				if child != nil || showNoChild {
+					v[key] = child
+				}
+				returnList = append(returnList, v)
+			}
+		}
+	}
+	return returnList
+}
+
+//获取切片里的值 若为nil 可设置默认值val
+func GetSliceByKey(args []interface{}, key int, val interface{}) interface{} {
+	var value interface{}
+	if args[key] != nil {
+		value = args[key]
+	} else {
+		value = val
+	}
+	return value
+}
+
+//有层级关系的切片,通过父级id查找所有子级id数组
+//parentId 父级id
+//parentIndex 父级索引名称
+//idIndex id索引名称
+func FindSonByParentId(list g.List, parentId int, parentIndex, idIndex string) g.List {
+	newList := make(g.List, 0, len(list))
+	for _, v := range list {
+		if gconv.Int(v[parentIndex]) == parentId {
+			newList = append(newList, v)
+			fList := FindSonByParentId(list, gconv.Int(v[idIndex]), parentIndex, idIndex)
+			newList = append(newList, fList...)
+		}
+	}
+	return newList
+}
+
+//获取最顶层 parent Id
+func GetTopPidList(list g.List, parentIndex, idIndex string) *garray.Array {
+	arr := garray.NewArray()
+	for _, v1 := range list {
+		tag := true
+		for _, v2 := range list {
+			if v1[parentIndex] == v2[idIndex] {
+				tag = false
+				break
+			}
+		}
+		if tag {
+			arr.PushRight(v1[parentIndex])
+		}
+	}
+	return arr.Unique()
+}
+
+//有层级关系的数组,通过子级fid查找所有父级数组
+func FindParentBySonPid(list g.List, id int, params ...interface{}) g.List {
+	args := make([]interface{}, 4)
+	for k, v := range params {
+		if k == 4 {
+			break
+		}
+		args[k] = v
+	}
+	var (
+		filter      = gconv.String(GetSliceByKey(args, 0, "filter")) //过滤键名
+		fPid        = gconv.String(GetSliceByKey(args, 1, "pid"))    //父级id字段键名
+		filterValue = GetSliceByKey(args, 2, nil)                    //过滤键值
+		fid         = gconv.String(GetSliceByKey(args, 3, "id"))     //id字段键名
+	)
+	rList := make(g.List, 0, len(list))
+	for _, v := range list {
+		if gconv.Int(v[fid]) == id {
+			if fv, ok := v[filter]; ok {
+				if reflect.DeepEqual(fv, filterValue) {
+					rList = append(rList, v)
+				}
+			} else {
+				rList = append(rList, v)
+			}
+			r := FindParentBySonPid(list, gconv.Int(v[fPid]), filter, fPid, filterValue, fid)
+			rList = append(rList, r...)
+		}
+	}
+	return rList
+}
+
+/**
+ * 根据id查询最顶层父级信息
+ * @param list 有层级关系的数组
+ * @param id 查找的id
+ * @param string fpid 父级id键名
+ * @param string fid 当前id键名
+ * @return g.Map
+ */
+func FindTopParent(list g.List, id int64, params ...interface{}) g.Map {
+	if len(list) == 0 {
+		return g.Map{}
+	}
+	args := make([]interface{}, 2)
+	for k, v := range params {
+		if k == 2 {
+			break
+		}
+		args[k] = v
+	}
+	var (
+		fPid = gconv.String(GetSliceByKey(args, 0, "pid")) //父级id字段键名
+		fid  = gconv.String(GetSliceByKey(args, 1, "id"))  //id字段键名
+	)
+	hasParent := true
+	top := g.Map{}
+	//找到要查找id值的数组
+	for _, v := range list {
+		if gconv.Int64(v[fid]) == gconv.Int64(id) {
+			top = v
+			break
+		}
+	}
+	for {
+		if !hasParent {
+			break
+		}
+		//查询最顶层
+		for _, v := range list {
+			if gconv.Int64(top[fPid]) == gconv.Int64(v[fid]) {
+				top = v
+				hasParent = true
+				break
+			}
+			hasParent = false
+		}
+	}
+	return top
+}

+ 136 - 0
library/utils.go

@@ -3,6 +3,7 @@ package library
 import (
 	"fmt"
 	"github.com/gogf/gf/crypto/gmd5"
+	"github.com/gogf/gf/database/gdb"
 	"github.com/gogf/gf/encoding/gcharset"
 	"github.com/gogf/gf/encoding/gjson"
 	"github.com/gogf/gf/encoding/gurl"
@@ -10,7 +11,10 @@ import (
 	"github.com/gogf/gf/frame/g"
 	"github.com/gogf/gf/net/ghttp"
 	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/text/gstr"
+	"github.com/gogf/gf/util/gconv"
 	"net"
+	"strings"
 )
 
 //密码加密
@@ -106,3 +110,135 @@ func StrToTimestamp(dateStr string) int64 {
 	}
 	return tm.Timestamp()
 }
+
+// GetDbConfig get db config
+func GetDbConfig() (cfg *gdb.ConfigNode, err error) {
+	cfg = g.DB().GetConfig()
+	err = ParseDSN(cfg)
+	return
+}
+
+// ParseDSN parses the DSN string to a Config
+func ParseDSN(cfg *gdb.ConfigNode) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = gerror.New(r.(string))
+		}
+	}()
+	dsn := cfg.Link
+	if dsn == "" {
+		return
+	}
+	foundSlash := false
+	// gfast:123456@tcp(192.168.0.212:3306)/gfast-v2
+	for i := len(dsn) - 1; i >= 0; i-- {
+		if dsn[i] == '/' {
+			foundSlash = true
+			var j, k int
+
+			// left part is empty if i <= 0
+			if i > 0 {
+				// [username[:password]@][protocol[(address)]]
+				// Find the last '@' in dsn[:i]
+				for j = i; j >= 0; j-- {
+					if dsn[j] == '@' {
+						// username[:password]
+						// Find the first ':' in dsn[:j]
+						for k = 0; k < j; k++ {
+							if dsn[k] == ':' {
+								cfg.Pass = dsn[k+1 : j]
+								cfg.User = dsn[:k]
+								break
+							}
+						}
+						break
+					}
+				}
+
+				// gfast:123456@tcp(192.168.0.212:3306)/gfast-v2
+				// [protocol[(address)]]
+				// Find the first '(' in dsn[j+1:i]
+				var h int
+				for k = j + 1; k < i; k++ {
+					if dsn[k] == '(' {
+						// dsn[i-1] must be == ')' if an address is specified
+						if dsn[i-1] != ')' {
+							if strings.ContainsRune(dsn[k+1:i], ')') {
+								panic("invalid DSN: did you forget to escape a param value?")
+							}
+							panic("invalid DSN: network address not terminated (missing closing brace)")
+						}
+						for h = k + 1; h < i-1; h++ {
+							if dsn[h] == ':' {
+								cfg.Host = dsn[k+1 : h]
+								cfg.Port = dsn[h+1 : i-1]
+								break
+							}
+						}
+						break
+					}
+				}
+			}
+			for j = i + 1; j < len(dsn); j++ {
+				if dsn[j] == '?' {
+					cfg.Name = dsn[i+1 : j]
+					break
+				} else {
+					cfg.Name = dsn[i+1:]
+				}
+			}
+			break
+		}
+	}
+	if !foundSlash && len(dsn) > 0 {
+		panic("invalid DSN: missing the slash separating the database name")
+	}
+	return
+}
+
+//获取附件真实路径
+func GetRealFilesUrl(r *ghttp.Request, path string) (realPath string, err error) {
+	if gstr.ContainsI(path, "http") {
+		realPath = path
+		return
+	}
+	realPath, err = GetDomain(r)
+	if err != nil {
+		return
+	}
+	realPath = realPath + path
+	return
+}
+
+//获取附件相对路径
+func GetFilesPath(fileUrl string) (path string, err error) {
+	if !gstr.ContainsI(fileUrl, "http") {
+		path = fileUrl
+		return
+	}
+	pathInfo, err := gurl.ParseURL(fileUrl, 32)
+	if err != nil {
+		g.Log().Error(err)
+		err = gerror.New("解析附件路径失败")
+		return
+	}
+	path = gstr.TrimLeft(pathInfo["path"], "/")
+	return
+}
+
+//货币转化为分
+func CurrencyLong(currency interface{}) int64 {
+	strArr := gstr.Split(gconv.String(currency), ".")
+	switch len(strArr) {
+	case 1:
+		return gconv.Int64(strArr[0]) * 100
+	case 2:
+		if len(strArr[1]) == 1 {
+			strArr[1] += "0"
+		} else if len(strArr[1]) > 2 {
+			strArr[1] = gstr.SubStr(strArr[1], 0, 2)
+		}
+		return gconv.Int64(strArr[0])*100 + gconv.Int64(strArr[1])
+	}
+	return 0
+}