Procházet zdrojové kódy

Merge branch 'bzq/dev' of eta_mini/eta_mini_crm into master

鲍自强 před 9 měsíci
rodič
revize
484fd7c8f1
66 změnil soubory, kde provedl 7379 přidání a 20 odebrání
  1. 8 0
      .gitignore
  2. 202 0
      controllers/base_auth.go
  3. 86 0
      controllers/base_common.go
  4. 49 0
      controllers/chart_permission.go
  5. 53 0
      controllers/classify.go
  6. 0 15
      controllers/default.go
  7. 21 0
      controllers/error.go
  8. 100 0
      controllers/seller.go
  9. 254 0
      controllers/sys_department.go
  10. 100 0
      controllers/sys_message_report.go
  11. 510 0
      controllers/sys_role.go
  12. 687 0
      controllers/sys_user.go
  13. 1169 0
      controllers/user.go
  14. 151 0
      controllers/user_login.go
  15. 447 0
      controllers/user_read_record.go
  16. 32 2
      go.mod
  17. 54 2
      main.go
  18. 16 0
      models/base.go
  19. 121 0
      models/chart_permission.go
  20. 37 0
      models/classify.go
  21. 16 0
      models/crm_config.go
  22. 43 0
      models/db.go
  23. 5 0
      models/request/chart_permission.go
  24. 23 0
      models/request/sys_department.go
  25. 5 0
      models/request/sys_message_report.go
  26. 20 0
      models/request/sys_role.go
  27. 44 0
      models/request/sys_user.go
  28. 44 0
      models/request/user.go
  29. 8 0
      models/response/chart_permission.go
  30. 13 0
      models/response/seller.go
  31. 7 0
      models/response/sys_department.go
  32. 20 0
      models/response/sys_menu.go
  33. 12 0
      models/response/sys_message_report.go
  34. 12 0
      models/response/sys_role.go
  35. 21 0
      models/response/sys_user.go
  36. 27 0
      models/response/user.go
  37. 6 0
      models/response/user_login.go
  38. 17 0
      models/response/user_read_record.go
  39. 184 0
      models/sys_department.go
  40. 89 0
      models/sys_menu.go
  41. 99 0
      models/sys_message_report.go
  42. 120 0
      models/sys_role.go
  43. 39 0
      models/sys_session.go
  44. 166 0
      models/sys_user.go
  45. 325 0
      models/user.go
  46. 41 0
      models/user_change_record.go
  47. 16 0
      models/user_chart_permission_mapping.go
  48. 98 0
      models/user_read_record.go
  49. 397 0
      routers/commentsRouter.go
  50. 58 1
      routers/router.go
  51. 189 0
      scheduler/task.go
  52. 31 0
      services/chart_permission.go
  53. 75 0
      services/sys_department.go
  54. 15 0
      services/sys_menu.go
  55. 8 0
      services/sys_session.go
  56. 45 0
      services/sys_user.go
  57. 19 0
      services/user.go
  58. 66 0
      utils/common.go
  59. 71 0
      utils/config.go
  60. 64 0
      utils/constants.go
  61. 37 0
      utils/des3.go
  62. 44 0
      utils/jwt.go
  63. 90 0
      utils/logs.go
  64. 33 0
      utils/redis.go
  65. 263 0
      utils/redis/cluster_redis.go
  66. 257 0
      utils/redis/standalone_redis.go

+ 8 - 0
.gitignore

@@ -1 +1,9 @@
 /conf/
+/.vscode/
+/etalogs/
+*.exe
+*.exe~
+go.sum
+scheduler/etalogs/
+scheduler/conf/
+*_test.go

+ 202 - 0
controllers/base_auth.go

@@ -0,0 +1,202 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/utils"
+	"fmt"
+	"net/http"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/beego/beego/v2/server/web"
+)
+
+type BaseAuthController struct {
+	web.Controller
+	SysUser *models.SysUser
+	Session *models.SysSession
+}
+
+func (c *BaseAuthController) Prepare() {
+	method := c.Ctx.Input.Method()
+	uri := c.Ctx.Input.URI()
+	if method != "HEAD" {
+		if method == "POST" || method == "GET" {
+			authorization := c.Ctx.Input.Header("authorization")
+			if authorization == "" {
+				c.JSON(models.BaseResponse{Ret: 408, Msg: "请重新授权!", ErrMsg: "请重新授权:Token is empty or account is empty"}, false, false)
+				c.StopRun()
+				return
+			}
+			tokenStr := authorization
+			tokenArr := strings.Split(tokenStr, "=")
+			token := tokenArr[1]
+
+			session, err := models.GetSysSessionByToken(token)
+			if err != nil {
+				if err == orm.ErrNoRows {
+					c.JSON(models.BaseResponse{Ret: 408, Msg: "信息已变更,请重新登陆!", ErrMsg: "Token 信息已变更:Token: " + token}, false, false)
+					c.StopRun()
+					return
+				}
+				c.JSON(models.BaseResponse{Ret: 408, Msg: "网络异常,请稍后重试!", ErrMsg: "获取用户信息异常,Eerr:" + err.Error()}, false, false)
+				c.StopRun()
+				return
+			}
+			if session == nil {
+				c.JSON(models.BaseResponse{Ret: 408, Msg: "网络异常,请稍后重试!", ErrMsg: "sesson is empty "}, false, false)
+				c.StopRun()
+				return
+			}
+			account := utils.MD5(session.UserName)
+			if !utils.CheckToken(account, token) {
+				c.JSON(models.BaseResponse{Ret: 408, Msg: "鉴权失败,请重新登录!", ErrMsg: "登录失效,请重新登陆!,CheckToken Fail"}, false, false)
+				c.StopRun()
+				return
+			}
+			if time.Now().After(session.ExpiredTime) {
+				c.JSON(models.BaseResponse{Ret: 408, Msg: "请重新登录!", ErrMsg: "获取用户信息异常"}, false, false)
+				c.StopRun()
+				return
+			}
+			sysUser, err := models.GetSysUserById(session.SysUserId)
+			if err != nil {
+				if err == orm.ErrNoRows {
+					c.JSON(models.BaseResponse{Ret: 408, Msg: "信息已变更,请重新登陆!", ErrMsg: "获取sysUser信息失败: " + strconv.Itoa(session.SysUserId)}, false, false)
+					c.StopRun()
+					return
+				}
+				c.JSON(models.BaseResponse{Ret: 408, Msg: "网络异常,请稍后重试!", ErrMsg: "获取sysUser信息异常,Err:" + err.Error()}, false, false)
+				c.StopRun()
+				return
+			}
+			if sysUser == nil {
+				c.JSON(models.BaseResponse{Ret: 408, Msg: "网络异常,请稍后重试!", ErrMsg: "sysUser is empty"}, false, false)
+				c.StopRun()
+				return
+			}
+			if !sysUser.IsEnabled {
+				c.JSON(models.BaseResponse{Ret: 408, Msg: "账户信息异常!", ErrMsg: "账户被禁用,不允许登陆!"}, false, false)
+				c.StopRun()
+				return
+			}
+			loginKey := fmt.Sprint(utils.CACHE_ACCESS_TOKEN_LOGIN, session.SysSessionId)
+			loginInfo, _ := utils.Rc.RedisString(loginKey)
+			if loginInfo == `` {
+				c.JSON(models.BaseResponse{Ret: 408, Msg: "超时未操作,系统自动退出!", ErrMsg: "超时未操作,系统自动退出!"}, false, false)
+				c.StopRun()
+				return
+			}
+			if loginInfo != "1" {
+				msg := "在其他网络登录。此客户端已退出登录。"
+				c.JSON(models.BaseResponse{Ret: 408, Msg: msg, ErrMsg: msg}, false, false)
+				c.StopRun()
+				return
+			}
+			c.SysUser = sysUser
+			c.Session = session
+
+			//接口权限校验
+			roleId := sysUser.SysRoleId
+			list, e := models.GetMenuButtonsByRoleId(roleId)
+			if e != nil {
+				c.JSON(models.BaseResponse{Ret: 403, Msg: "获取接口权限出错!", ErrMsg: "获取接口权限出错!"}, false, false)
+				c.StopRun()
+				return
+			}
+			var api string
+			for _, v := range list {
+				api += v.Api + "&"
+			}
+			api = strings.TrimRight(api, "&")
+			uri = strings.Replace(uri, "/adminapi", "", 1)
+			uris := strings.Split(uri, "?")
+			uri = uris[0]
+			//fmt.Println("uri:", uri)
+			apis := strings.Split(api, "&")
+			apiMap := make(map[string]bool, 0)
+			for _, s := range apis {
+				apiMap[s] = true
+			}
+			if !apiMap[uri] && !utils.NoAuthApiMap[uri] {
+				c.JSON(models.BaseResponse{Ret: 403, Msg: "无权访问!", ErrMsg: "无权访问!"}, false, false)
+				c.StopRun()
+				return
+			}
+
+		}
+	}
+}
+
+func (c *BaseAuthController) ServeJSON(encoding ...bool) {
+	var (
+		hasIndent   = false
+		hasEncoding = false
+	)
+	if web.BConfig.RunMode == web.PROD {
+		hasIndent = false
+	}
+	if len(encoding) > 0 && encoding[0] == true {
+		hasEncoding = true
+	}
+	if c.Data["json"] == nil {
+		body := "接口:" + "URI:" + c.Ctx.Input.URI() + ";无返回值"
+		fmt.Println(body)
+		utils.ApiLog.Notice(body)
+		return
+	}
+	baseRes := c.Data["json"].(*models.BaseResponse)
+	if baseRes != nil && baseRes.Ret != 408 {
+		body, _ := json.Marshal(baseRes)
+		var requestBody string
+		method := c.Ctx.Input.Method()
+		if method == "GET" {
+			requestBody = c.Ctx.Request.RequestURI
+		} else {
+			//requestBody, _ = url.QueryUnescape(string(c.Ctx.Input.RequestBody))
+			requestBody = string(c.Ctx.Input.RequestBody)
+		}
+		if baseRes.Ret != 200 {
+			body := "URI:" + c.Ctx.Input.URI() + "<br/> " + "Params" + requestBody + " <br/>" + "ErrMsg:" + baseRes.ErrMsg + ";<br/>Msg:" + baseRes.Msg + ";<br/> Body:" + string(body) + "<br/>" + c.SysUser.SysRealName
+			fmt.Println(body)
+			utils.ApiLog.Notice(body)
+		}
+		if baseRes.IsAddLog && c.SysUser != nil {
+			return
+		}
+	}
+
+	c.JSON(c.Data["json"], hasIndent, hasEncoding)
+}
+
+func (c *BaseAuthController) JSON(data interface{}, hasIndent bool, coding bool) error {
+	c.Ctx.Output.Header("Content-Type", "application/json; charset=utf-8")
+	desEncrypt := utils.DesBase64Encrypt([]byte(utils.DesKey), utils.DesKeySalt)
+	c.Ctx.Output.Header("Dk", string(desEncrypt)) // des3加解密key
+	// 设置Cookie为HTTPOnly
+	c.Ctx.SetCookie("", "", -1, "/", "", false, true, "")
+
+	var content []byte
+	var err error
+	if hasIndent {
+		content, err = json.MarshalIndent(data, "", "  ")
+	} else {
+		content, err = json.Marshal(data)
+	}
+
+	if err != nil {
+		http.Error(c.Ctx.Output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
+		return err
+	}
+	if utils.RunMode != "debug" {
+		content = utils.DesBase64Encrypt(content, utils.DesKey)
+		content = []byte(`"` + string(content) + `"`)
+	}
+	if coding {
+		content = []byte(utils.StringsToJSON(string(content)))
+	}
+	return c.Ctx.Output.Body(content)
+}

+ 86 - 0
controllers/base_common.go

@@ -0,0 +1,86 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/utils"
+	"fmt"
+	"net/http"
+	"net/url"
+
+	"github.com/beego/beego/v2/server/web"
+)
+
+type BaseCommonController struct {
+	web.Controller
+}
+
+func (c *BaseCommonController) Prepare() {
+	var requestBody string
+	method := c.Ctx.Input.Method()
+	if method == "GET" {
+		requestBody = c.Ctx.Request.RequestURI
+	} else {
+		requestBody, _ = url.QueryUnescape(string(c.Ctx.Input.RequestBody))
+	}
+
+	ip := c.Ctx.Input.IP()
+	utils.ApiLog.Info("uri:%s, requestBody:%s, ip:%s", c.Ctx.Input.URI(), requestBody, ip)
+}
+
+func (c *BaseCommonController) ServeJSON(encoding ...bool) {
+	var (
+		hasIndent   = false
+		hasEncoding = false
+	)
+	if web.BConfig.RunMode == web.PROD {
+		hasIndent = false
+	}
+	if len(encoding) > 0 && encoding[0] == true {
+		hasEncoding = true
+	}
+	if c.Data["json"] == nil {
+		utils.ApiLog.Notice("异常提醒:"+utils.RunMode, "接口:"+"URI:"+c.Ctx.Input.URI()+";无返回值")
+		return
+	}
+	baseRes := c.Data["json"].(*models.BaseResponse)
+	if baseRes != nil && !baseRes.Success {
+		utils.ApiLog.Notice("异常提醒:"+utils.RunMode, "接口:"+"URI:"+c.Ctx.Input.URI()+";ErrMsg:"+baseRes.ErrMsg+";Msg"+baseRes.Msg)
+		return
+	}
+
+	c.JSON(c.Data["json"], hasIndent, hasEncoding)
+}
+
+func (c *BaseCommonController) JSON(data interface{}, hasIndent bool, coding bool) error {
+	c.Ctx.Output.Header("Content-Type", "application/json; charset=utf-8")
+	desEncrypt := utils.DesBase64Encrypt([]byte(utils.DesKey), utils.DesKeySalt)
+	c.Ctx.Output.Header("Dk", string(desEncrypt)) // des3加解密key
+	// 设置Cookie为HTTPOnly
+	c.Ctx.SetCookie("", "", -1, "/", "", false, true, "")
+
+	var content []byte
+	var err error
+	if hasIndent {
+		content, err = json.MarshalIndent(data, "", "  ")
+	} else {
+		content, err = json.Marshal(data)
+	}
+	if err != nil {
+		http.Error(c.Ctx.Output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
+		return err
+	}
+	params := c.Ctx.Input.Params()
+	fmt.Println("params")
+	fmt.Println(params)
+	// 如果不是debug分支的话,那么需要加密返回
+	if utils.RunMode != "debug" {
+		content = utils.DesBase64Encrypt(content, utils.DesKey)
+		// get请求时,不加双引号就获取不到数据,不知道什么原因,所以还是在前后加上双引号吧
+		content = []byte(`"` + string(content) + `"`)
+	}
+	if coding {
+		content = []byte(utils.StringsToJSON(string(content)))
+	}
+	return c.Ctx.Output.Body(content)
+}

+ 49 - 0
controllers/chart_permission.go

@@ -0,0 +1,49 @@
+package controllers
+
+import (
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/models/response"
+	"eta/eta_mini_crm/services"
+)
+
+type ChartPermissionController struct {
+	BaseAuthController
+}
+
+// List
+// @Title 系统品种列表
+// @Description 系统品种列表
+// @Param   UserId   query   int  true       "角色ID"
+// @Success 200 {object} models.LoginResp
+// @router /list [get]
+func (this *ChartPermissionController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	userId, _ := this.GetInt("UserId")
+
+	items, err := models.GetChartPermissionList()
+	if err != nil {
+		br.Msg = "权限列表获取失败"
+		br.ErrMsg = "权限列表获取失败,系统错误,Err:" + err.Error()
+		return
+	}
+	treeList := services.GetChartPermissionListTree(items, 0)
+	resp := new(response.ChartPermissionListresp)
+	if userId > 0 {
+		ids, err := models.GetChartPermissionIdByUserId(userId)
+		if err != nil {
+			br.Msg = "权限列表获取失败"
+			br.ErrMsg = "权限列表获取失败,系统错误,Err:" + err.Error()
+		}
+		resp.SelectedList = ids
+	}
+	resp.List = treeList
+
+	br.Ret = 200
+	br.Data = resp
+	br.Msg = "列表获取成功"
+	br.Success = true
+}

+ 53 - 0
controllers/classify.go

@@ -0,0 +1,53 @@
+package controllers
+
+import "eta/eta_mini_crm/models"
+
+type ClassifyController struct {
+	BaseAuthController
+}
+
+// List
+// @Title 系统类别列表
+// @Description 系统类别列表
+// @Success 200 {object} models.LoginResp
+// @router /list [get]
+func (this *ClassifyController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	classifyList, err := models.GetClassifyList()
+	if err != nil {
+		br.Msg = "分类列表获取失败"
+		br.ErrMsg = "分类列表获取失败,系统错误,Err:" + err.Error()
+		return
+	}
+	classifyList = getClassifyTree(classifyList, 0)
+
+	br.Data = classifyList
+	br.Ret = 200
+	br.Msg = "列表获取成功"
+	br.Success = true
+}
+
+// getClassifyTree 递归获取分类树
+func getClassifyTree(classifyList []*models.ClassifyView, parentId int) []*models.ClassifyView {
+	res := make([]*models.ClassifyView, 0)
+	for _, v := range classifyList {
+		if v.ParentId == parentId {
+			t := new(models.ClassifyView)
+			t.Id = v.Id
+			t.ClassifyName = v.ClassifyName
+			t.ParentId = v.ParentId
+			t.CreateTime = v.CreateTime
+			t.ModifyTime = v.ModifyTime
+			t.Enabled = v.Enabled
+			t.ClassifyLabel = v.ClassifyLabel
+			t.Child = getClassifyTree(classifyList, v.Id)
+			res = append(res, t)
+		}
+	}
+	return res
+}

+ 0 - 15
controllers/default.go

@@ -1,15 +0,0 @@
-package controllers
-
-import (
-	beego "github.com/beego/beego/v2/server/web"
-)
-
-type MainController struct {
-	beego.Controller
-}
-
-func (c *MainController) Get() {
-	c.Data["Website"] = "beego.vip"
-	c.Data["Email"] = "astaxie@gmail.com"
-	c.TplName = "index.tpl"
-}

+ 21 - 0
controllers/error.go

@@ -0,0 +1,21 @@
+package controllers
+
+import "eta/eta_mini_crm/models"
+
+// ErrorController
+// @Description: 该控制器处理页面错误请求
+type ErrorController struct {
+	BaseCommonController
+}
+
+func (c *ErrorController) Error404() {
+	c.Data["content"] = "很抱歉您访问的地址或者方法不存在"
+	//c.TplName = "error/404.html"
+
+	br := new(models.BaseResponse).Init()
+
+	br.Msg = "您访问的资源不存在"
+	br.Ret = 404
+	c.Data["json"] = br
+	c.ServeJSON()
+}

+ 100 - 0
controllers/seller.go

@@ -0,0 +1,100 @@
+package controllers
+
+import (
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/models/response"
+)
+
+type SellerController struct {
+	BaseAuthController
+}
+
+// List
+// @Title 获取部门下销售
+// @Description 获取部门下销售接口
+// @Param   AllEnabled   query   bool  true       "是否获取包含禁用的用户"
+// @Success 200 {object} company.DepartmentGroupSellersResp
+// @router /list [get]
+func (this *SellerController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	getAllEnabled, _ := this.GetBool("AllEnabled", false)
+	enabled := true    //默认只获取正常状态的用户
+	if getAllEnabled { //获取所有状态的用户
+		enabled = false
+	}
+	departmentList, err := models.GetSysDepartmentList()
+	if err != nil {
+		br.Msg = "获取部门失败"
+		br.ErrMsg = "获取部门失败,系统异常,Err:" + err.Error()
+		return
+	}
+	seller, err := models.GetSysUserListByIsEnabled(enabled)
+	if err != nil {
+		br.Msg = "获取销售失败"
+		br.ErrMsg = "获取销售失败,系统异常,Err:" + err.Error()
+		return
+	}
+	// 遍历获取销售的层级结构
+	list := make([]*response.DepartmentSellers, 0)
+	for _, dep1 := range departmentList {
+		if dep1.ParentId == 0 {
+			v1 := new(response.DepartmentSellers)
+			v1.SysDepartmentId = dep1.SysDepartmentId
+			v1.SysDepartmentName = dep1.SysDepartmentName
+			for _, s := range seller {
+				if s.SysDepartmentId1 == dep1.SysDepartmentId && s.SysDepartmentId2 == 0 {
+					s1 := new(response.DepartmentSellers)
+					s1.SysUserId = s.SysUserId
+					s1.SysRealName = s.SysRealName
+					v1.ChildrenList = append(v1.ChildrenList, s1)
+				}
+			}
+			for _, dep2 := range departmentList {
+				if dep2.ParentId == dep1.SysDepartmentId {
+					v2 := new(response.DepartmentSellers)
+					v2.SysDepartmentId = dep2.SysDepartmentId
+					v2.SysDepartmentName = dep2.SysDepartmentName
+					for _, s := range seller {
+						if s.SysDepartmentId2 == dep2.SysDepartmentId && s.SysDepartmentId3 == 0 {
+							s1 := new(response.DepartmentSellers)
+							s1.SysUserId = s.SysUserId
+							s1.SysRealName = s.SysRealName
+							v2.ChildrenList = append(v2.ChildrenList, s1)
+						}
+					}
+					v1.ChildrenList = append(v1.ChildrenList, v2)
+					for _, dep3 := range departmentList {
+						if dep3.ParentId == dep2.SysDepartmentId {
+							v3 := new(response.DepartmentSellers)
+							v3.SysDepartmentId = dep3.SysDepartmentId
+							v3.SysDepartmentName = dep3.SysDepartmentName
+							for _, s := range seller {
+								if s.SysDepartmentId3 == dep3.SysDepartmentId {
+									s1 := new(response.DepartmentSellers)
+									s1.SysUserId = s.SysUserId
+									s1.SysRealName = s.SysRealName
+									v3.ChildrenList = append(v3.ChildrenList, s1)
+								}
+							}
+							v2.ChildrenList = append(v2.ChildrenList, v3)
+						}
+					}
+				}
+			}
+			list = append(list, v1)
+		}
+
+	}
+	resp := new(response.DepartmentGroupSellersResp)
+	resp.List = list
+
+	br.Msg = "获取成功"
+	br.Success = true
+	br.Ret = 200
+	br.Data = resp
+}

+ 254 - 0
controllers/sys_department.go

@@ -0,0 +1,254 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/models/request"
+	"eta/eta_mini_crm/models/response"
+	"eta/eta_mini_crm/services"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type SysDepartmentController struct {
+	BaseAuthController
+}
+
+// @Title 获取部门列表
+// @Description 获取部门列表接口
+// @Success 200 {object} system.SysDepartmentListResp
+// @router /list [get]
+func (this *SysDepartmentController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	departmentList, err := models.GetSysDepartmentList()
+	if err != nil {
+		br.Msg = "获取部门列表失败"
+		br.ErrMsg = "获取部门列表失败,Err" + err.Error()
+		return
+	}
+	list := services.GetSysDepartmentTree(departmentList, 0)
+
+	resp := new(response.SysDepartmentListResp)
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 新增部门
+// @Description 新增部门接口
+// @Param	request	body system.SysDepartmentAddReq true "type json string"
+// @Success 200 新增成功
+// @router /add [post]
+func (this *SysDepartmentController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.SysDepartmentAddReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.SysDepartmentNames) <= 0 {
+		br.Msg = "部门名称不能为空"
+		return
+	}
+	for _, dep := range req.SysDepartmentNames {
+		count, err := models.GetSysDepartmentCountByParentId(req.SysDepartmentId, dep)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		if count <= 0 {
+			sysDepartment := &models.SysDepartment{}
+			sysDepartment.SysDepartmentName = dep
+			sysDepartment.Level = req.Level
+			sysDepartment.ParentId = req.SysDepartmentId
+			err = sysDepartment.Add()
+			if err != nil {
+				br.Msg = "新增失败"
+				br.ErrMsg = "新增失败,Err:" + err.Error()
+				return
+			}
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "新增成功"
+}
+
+// @Title 编辑部门
+// @Description 编辑部门接口
+// @Param	request	body request.SysDepartmentEditReq true "type json string"
+// @Success 200 编辑成功
+// @router /edit [post]
+func (this *SysDepartmentController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.SysDepartmentEditReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SysDepartmentId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,SysDepartmentId 小于等于0 "
+		return
+	}
+	if req.SysDepartmentName == "" {
+		br.Msg = "名称不能为空"
+		return
+	}
+	count, err := models.GetSysDepartmentCountById(req.SysDepartmentId)
+	if err != nil && err != orm.ErrNoRows {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	if count <= 0 {
+		br.Msg = "分组不存在,请刷新重试"
+		return
+	}
+	oldSysDepartment, err := models.GetSysDepartmentById(req.SysDepartmentId)
+	if err != nil && err != orm.ErrNoRows {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	newSysDepartment, err := models.GetSysDepartmentByName(req.SysDepartmentName)
+	if err != nil && err != orm.ErrNoRows {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	if newSysDepartment != nil && newSysDepartment.SysDepartmentId != oldSysDepartment.SysDepartmentId {
+		br.Msg = "分组名称已存在,请重新输入"
+		return
+	}
+	sysDepartment := &models.SysDepartment{}
+	sysDepartment.SysDepartmentId = req.SysDepartmentId
+	sysDepartment.SysDepartmentName = req.SysDepartmentName
+	err = sysDepartment.Update([]string{"sys_department_name"})
+	if err != nil {
+		br.Msg = "编辑失败,系统错误"
+		br.Msg = "编辑失败,系统错误,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "编辑成功"
+}
+
+// @Title 删除分组
+// @Description 删除分组接口
+// @Param	request	body request.SysDepartmentDeleteReq true "type json string"
+// @Success 200 删除成功
+// @router /delete [post]
+func (this *SysDepartmentController) Delete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.SysDepartmentDeleteReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SysDepartmentId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,SysDepartmentId 小于等于0 "
+		return
+	}
+	if req.Level <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,Level 小于等于0 "
+		return
+	}
+	err = services.DeleteSysDepartmentById(req.SysDepartmentId, req.Level)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,系统异常,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+}
+
+// SetSort
+// @Title 分组排序
+// @Description  分组排序
+// @Param	request	body request.SysDepartmentSortReq true "type json string"
+// @Success 200 修改成功
+// @router /set_sort [post]
+func (this *SysDepartmentController) SetSort() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.SysDepartmentSortReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	// 一级排序
+	if len(req.DepartmentIds1) > 0 {
+		err = models.UpdateDepartmentSortByIds(req.DepartmentIds1)
+		if err != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "批量更新部门排序失败, Err: " + err.Error()
+			return
+		}
+	}
+	// 二级级排序
+	if len(req.DepartmentIds2) > 0 {
+		err = models.UpdateDepartmentSortByIds(req.DepartmentIds2)
+		if err != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "批量更新部门排序失败, Err: " + err.Error()
+			return
+		}
+	}
+	// 三级级排序
+	if len(req.DepartmentIds3) > 0 {
+		err = models.UpdateDepartmentSortByIds(req.DepartmentIds3)
+		if err != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "批量更新部门排序失败, Err: " + err.Error()
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 100 - 0
controllers/sys_message_report.go

@@ -0,0 +1,100 @@
+package controllers
+
+import (
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/models/response"
+	"eta/eta_mini_crm/utils"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type SysMessageReportController struct {
+	BaseAuthController
+}
+
+// @Title 阅读消息
+// @Description 阅读消息
+// @Param	request	body system.SysRoleDeleteReq true "type json string"
+// @Success 200 查看成功
+// @router /read [post]
+func (this *SysMessageReportController) Read() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	err := models.UpdateReadSysMessageReportByUserId(this.SysUser.SysUserId)
+	if err != nil {
+		br.Msg = "更新消息状态失败"
+		br.ErrMsg = "更新消息状态失败,系统错误,Err:" + err.Error()
+		return
+	}
+
+	br.Msg = "查看成功"
+	br.Success = true
+	br.Ret = 200
+}
+
+// List
+// @Title 系统消息列表
+// @Description 系统消息列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Success 200 {object} response.SysRoleListResp
+// @router /list [get]
+func (this *SysMessageReportController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	total, err := models.GetSysMessageReportCountBySysUserId(this.SysUser.SysUserId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	list, err := models.GetSysMessageReportBySysUserId(this.SysUser.SysUserId, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	messageList := make([]*models.SysMessageReportView, 0)
+	for _, v := range list {
+		messageList = append(messageList, &models.SysMessageReportView{
+			SysMessageReportId: v.SysMessageReportId,
+			UserId:             v.UserId,
+			ReceiveSysUserId:   v.ReceiveSysUserId,
+			MessageType:        v.MessageType,
+			Content:            v.Content,
+			Remark:             v.Remark,
+			IsRead:             v.IsRead,
+			CreateTime:         v.CreateTime.Format(utils.FormatDateTime),
+			ModifyTime:         v.ModifyTime.Format(utils.FormatDateTime),
+		})
+
+	}
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(response.SysMessageListResp)
+	resp.List = messageList
+	resp.Paging = page
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 510 - 0
controllers/sys_role.go

@@ -0,0 +1,510 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/models/request"
+	"eta/eta_mini_crm/models/response"
+	"eta/eta_mini_crm/services"
+	"eta/eta_mini_crm/utils"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type SysRoleController struct {
+	BaseAuthController
+}
+
+// List
+// @Title 系统角色列表
+// @Description 系统角色列表
+// @Param	request	body UserLoginReq true "type json string"
+// @Success 200 {object} response.SysRoleListResp
+// @router /list [get]
+func (this *SysRoleController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	total, err := models.GetSysRoleListCount()
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	list, err := models.GetSysRoleList(startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(response.SysRoleListResp)
+	resp.List = list
+	resp.Paging = page
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// AllList
+// @Title 系统所有角色列表
+// @Description 系统所有角色列表
+// @Success 200 {object} response.SysRoleListResp
+// @router /allList [get]
+func (this *SysRoleController) AllList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	list, err := models.GetSysRoleListNoPage()
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	resp := new(response.SysRoleListResp)
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 删除角色
+// @Description 删除角色接口
+// @Param	request	body system.SysRoleDeleteReq true "type json string"
+// @Success 200 删除成功
+// @router /delete [post]
+func (this *SysRoleController) Delete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.SysRoleDeleteReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SysRoleId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,GroupId 小于等于0 "
+		return
+	}
+
+	role, err := models.GetSysRoleById(req.SysRoleId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "角色不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "删除失败"
+		br.ErrMsg = "获取角色信息失败, Err: " + err.Error()
+		return
+	}
+	if role.SysRoleName == "admin" {
+		br.Msg = "admin角色不可删除"
+		return
+	}
+	count, err := models.GetSysUserCountByRoleId(req.SysRoleId)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "获取用户数量失败,Err: " + err.Error()
+		return
+	}
+	if count > 0 {
+		br.Msg = "角色绑定用户,不可删除"
+		return
+	}
+	err = models.DeleteSysRoleById(req.SysRoleId)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.Msg = "删除角色失败,系统错误,Err: " + err.Error()
+		return
+	}
+
+	br.Msg = "删除成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// @Title 新增角色
+// @Description 新增角色接口
+// @Param	request	body request.SysRoleAddReq true "type json string"
+// @Success 200 新增成功
+// @router /add [post]
+func (this *SysRoleController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.SysRoleAddReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.SysRoleName == "" {
+		br.Msg = "角色名称不能为空"
+		return
+	}
+	count, err := models.GetSysRoleCountByRoleName(req.SysRoleName)
+	if err != nil {
+		br.Msg = "新增角色失败"
+		br.ErrMsg = "新增角色失败,系统异常,Err:" + err.Error()
+		return
+	}
+	if count > 0 {
+		br.Msg = "角色已存在,请重新输入"
+		return
+	}
+	sysRole := &models.SysRole{}
+	sysRole.SysRoleName = req.SysRoleName
+	sysRole.CreateTime = time.Now()
+	sysRole.ModifyTime = time.Now()
+	err = sysRole.Add()
+	if err != nil {
+		br.Msg = "新增角色失败"
+		br.ErrMsg = "新增角色失败,系统异常,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "新增角色成功"
+	br.Success = true
+}
+
+// @Title 新增角色
+// @Description 新增角色接口
+// @Param	request	body request.SysRoleAddReq true "type json string"
+// @Success 200 新增成功
+// @router /edit [post]
+func (this *SysRoleController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.SysRoleEditReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SysRoleId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,GroupId 小于等于0 "
+		return
+	}
+	if req.SysRoleName == "" {
+		br.Msg = "分组名称不能为空"
+		return
+	}
+
+	sysRole, err := models.GetSysRoleById(req.SysRoleId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "角色不存在,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取角色失败, Err:" + err.Error()
+		return
+	}
+	if sysRole.SysRoleName == "admin" {
+		br.Msg = "admin角色不可编辑"
+		return
+	}
+	oldSysRole, err := models.GetSysRoleByRoleName(req.SysRoleName)
+	if err != nil && err != orm.ErrNoRows {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取角色失败, Err:" + err.Error()
+		return
+	}
+	if oldSysRole != nil && oldSysRole.SysRoleId != req.SysRoleId {
+		br.Msg = "名称已存在,请重新输入"
+		return
+	}
+	err = models.UpdateSysUserRoleByRoleId(req.SysRoleId, req.SysRoleName)
+	if err != nil {
+		br.Msg = "编辑失败"
+		br.ErrMsg = "编辑角色失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "编辑成功"
+}
+
+// ButtonList
+// @Title 角色-按钮权限列表
+// @Description 角色-按钮权限列表
+// @Param   RoleId   query   int  true       "角色Id"
+// @Success 200 {object} system.SysRoleListResp
+// @router /menu/buttons [get]
+func (this *SysRoleController) ButtonList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	roleId := sysUser.SysRoleId
+	list, e := models.GetMenuButtonsByRoleId(roleId)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取角色按钮权限失败, Err: " + e.Error()
+		return
+	}
+
+	buttonList := make([]*response.SysMenuButtonResp, 0)
+	for _, v := range list {
+		// 把菜单过滤
+		if v.MenuType == 1 {
+			continue
+		}
+		tmp := new(response.SysMenuButtonResp)
+		tmp.SysMenuId = v.SysMenuId
+		tmp.ParentId = v.ParentId
+		tmp.Name = v.Name
+		tmp.MenuType = v.MenuType
+		tmp.ButtonCode = v.ButtonCode
+		buttonList = append(buttonList, tmp)
+	}
+
+	br.Data = buttonList
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// @Title 获取用户权限菜单
+// @Description 获取用户权限菜单接口
+// @Success 200 {object} system.MenuListResp
+// @router /menu/list [get]
+func (this *SysRoleController) SysMenuList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	roleId := sysUser.SysRoleId
+	if roleId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+	list, err := models.GetMenuListByRoleIds(roleId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	// items := make([]*models.SysMenuItem, 0)
+	// for _, v := range list {
+	// 	t := &models.SysMenuItem{
+	// 		SysMenuId:  v.SysMenuId,
+	// 		ParentId:   v.ParentId,
+	// 		Name:       v.Name,
+	// 		Level:      v.Level,
+	// 		Sort:       v.Sort,
+	// 		Path:       v.Path,
+	// 		IconPath:   v.IconPath,
+	// 		ButtonCode: v.ButtonCode,
+	// 		Children:   make([]*models.SysMenuItem, 0),
+	// 	}
+	// 	items = append(items, t)
+	// }
+	// 递归返回树形结构
+	list = services.GetMenuTreeRecursive(list, 0)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// SysRoleMenuAuthSave
+// @Title 角色设置权限-保存
+// @Description 角色设置权限-保存
+// @Param	request	body RoleMenusSaveReq true "type json string"
+// @Success 200 {object} system.SysRoleListResp
+// @router /menu/auth_save [post]
+func (this *SysRoleController) SysRoleMenuAuthSave() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.RoleMenusSaveReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SysRoleId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+	if len(req.SysMenuIds) == 0 {
+		br.Msg = "请选择菜单"
+		return
+	}
+	halfMap := make(map[int]struct{})
+	for _, v := range req.HalfMenuIds {
+		halfMap[v] = struct{}{}
+	}
+
+	items := make([]*models.SysRoleMenuMapping, 0)
+	for _, v := range req.SysMenuIds {
+		if _, ok := halfMap[v]; ok {
+			continue
+		}
+		t := new(models.SysRoleMenuMapping)
+		t.SysMenuId = v
+		t.SysRoleId = req.SysRoleId
+		items = append(items, t)
+	}
+	for _, v := range req.HalfMenuIds {
+		t := new(models.SysRoleMenuMapping)
+		t.SysMenuId = v
+		t.Type = 1
+		t.SysRoleId = req.SysRoleId
+		items = append(items, t)
+	}
+	if e := models.CreateMultiSysRoleMenu(req.SysRoleId, items); e != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存角色菜单权限失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "设置成功"
+}
+
+// SysRoleMenuAuthList
+// @Title 角色设置权限-菜单列表
+// @Description 角色设置权限-菜单列表
+// @Param   RoleId   query   int  true       "角色Id"
+// @Success 200 {object} system.SysRoleListResp
+// @router /menu/auth_list [get]
+func (this *SysRoleController) SysRoleMenuAuthList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	resp := new(response.SysMenuListResp)
+	resp.ChoiceList = make([]int, 0)
+	resp.HalfChoiceList = make([]int, 0)
+	resp.List = make([]*models.SysMenuItem, 0)
+
+	// 角色勾选的权限
+	roleId, _ := this.GetInt("SysRoleId", 0)
+	if roleId > 0 {
+		relates, e := models.GetSysRoleMenuByRoleId(roleId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取角色关联菜单失败, Err: " + e.Error()
+			return
+		}
+		for _, r := range relates {
+			if r.Type == 1 {
+				resp.HalfChoiceList = append(resp.HalfChoiceList, r.SysMenuId)
+				continue
+			}
+			resp.ChoiceList = append(resp.ChoiceList, r.SysMenuId)
+		}
+	}
+
+	sysRole, err := models.GetSysRoleById(roleId)
+	if err == nil {
+		if sysRole.SysRoleName == "admin" {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "获取成功"
+			return
+		}
+	}
+
+	order := `sort ASC, create_time DESC, sys_menu_id DESC`
+	list, e := models.GetSysMenuItemsByCondition(``, make([]interface{}, 0), []string{}, order)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取菜单列表失败, Err: " + e.Error()
+		return
+	}
+
+	items := make([]*models.SysMenuItem, 0)
+	for _, v := range list {
+		t := &models.SysMenuItem{
+			SysMenuId:  v.SysMenuId,
+			ParentId:   v.ParentId,
+			Name:       v.Name,
+			Level:      v.Level,
+			Sort:       v.Sort,
+			Path:       v.Path,
+			IconPath:   v.IconPath,
+			ButtonCode: v.ButtonCode,
+			Children:   make([]*models.SysMenuItem, 0),
+		}
+		items = append(items, t)
+	}
+
+	// 递归返回树形结构
+	items = services.GetMenuTreeRecursive(items, 0)
+
+	resp.List = items
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 687 - 0
controllers/sys_user.go

@@ -0,0 +1,687 @@
+package controllers
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/models/request"
+	"eta/eta_mini_crm/models/response"
+	"eta/eta_mini_crm/services"
+	"eta/eta_mini_crm/utils"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type SysUserController struct {
+	BaseAuthController
+}
+
+// Add
+// @Title 系统用户添加
+// @Description 系统用户添加
+// @Param	request	body request.UserLoginReq true "type json string"
+// @Success 200 {object} models.LoginResp
+// @router /add [post]
+func (this *SysUserController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.SysUserInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SysUserName == "" {
+		br.Msg = "请输入账号"
+		return
+	}
+	if req.Password == "" {
+		br.Msg = "请输入密码"
+		return
+	}
+	if req.RealName == "" {
+		br.Msg = "请输入姓名"
+		return
+	}
+	// 手机号和邮箱必填一个
+	req.Phone = strings.TrimSpace(req.Phone)
+	req.Email = strings.TrimSpace(req.Email)
+	if req.Phone == "" && req.Email == "" {
+		br.Msg = "至少输入一个手机号或邮箱"
+		return
+	}
+	if req.Phone != "" {
+		if req.AreaCode == "86" {
+			if !utils.ValidateMobileFormatat(req.Phone) {
+				br.Msg = "手机号格式有误, 请检查"
+				return
+			}
+		}
+	}
+	if req.Email != "" {
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "邮箱格式有误, 请检查"
+			return
+		}
+	}
+	if req.SysDepartmentId <= 0 {
+		br.Msg = "请选择部门"
+		return
+	}
+	_, err = models.GetSysDepartmentById(req.SysDepartmentId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "所选部门不存在"
+			return
+		}
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取部门数据失败,Err:" + err.Error()
+		return
+	}
+
+	sysRole, err := models.GetSysRoleById(req.SysRoleId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "所选角色不存在"
+			return
+		}
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取角色数据失败,Err:" + err.Error()
+		return
+	}
+
+	// 校验系统用户的密码
+	var pwd string
+	{
+		pwdByte, err := base64.StdEncoding.DecodeString(req.Password)
+		if err != nil {
+			br.Msg = "解析数据失败"
+			br.ErrMsg = "解析数据失败,Err:" + err.Error()
+			return
+		}
+		originPwd := string(pwdByte)
+		if !utils.CheckPwd(originPwd) {
+			br.Msg = "密码格式错误,请重新输入"
+			return
+		}
+		pwd = utils.MD5(originPwd)
+	}
+
+	count, err := models.GetSysUserCountBySysUserName(req.SysUserName)
+	if err != nil && err != orm.ErrNoRows {
+		br.Msg = "添加用户失败,系统错误"
+		br.ErrMsg = "添加用户失败,系统错误 Err:" + err.Error()
+		return
+	}
+	if count > 0 {
+		br.Msg = "添加用户失败,用户已存在"
+		return
+	}
+	sysUser := &models.SysUser{}
+	sysUser.SysUserName = req.SysUserName
+	sysUser.SysRealName = req.RealName
+	sysUser.Password = pwd
+	sysUser.AreaCode = req.AreaCode
+	sysUser.Phone = req.Phone
+	sysUser.Email = req.Email
+	sysUser.SysDepartmentId = req.SysDepartmentId
+	sysUser.SysRoleId = req.SysRoleId
+	sysUser.SysRoleName = sysRole.SysRoleName
+	sysUser.Province = req.Province
+	sysUser.City = req.City
+	sysUser.IsEnabled = req.IsEnabled
+	sysUser.CreateTime = time.Now()
+	sysUser.ModifyTime = time.Now()
+
+	depPathIds, err := services.GetSysDepartmentPathIdsById(req.SysDepartmentId)
+	if err != nil {
+		br.Msg = "编辑角色失败"
+		br.ErrMsg = "获得部门路径失败,Err:" + err.Error()
+		return
+	}
+	err = models.SaveSysUser(sysUser, depPathIds)
+	if err != nil {
+		br.Msg = "用户添加失败"
+		br.ErrMsg = "用户添加失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "添加成功"
+}
+
+// Detail
+// @Title 系统用户详情信息
+// @Description 用户详情信息
+// @Param   SysUserId   query   int  true       "系统用户id"
+// @Success 200 {object} models.LoginResp
+// @router /detail [get]
+func (this *SysUserController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUserId, err := this.GetInt("SysUserId")
+	if err != nil {
+		br.Msg = "参数解析错误"
+		return
+	}
+	if sysUserId <= 0 {
+		br.Msg = "用户参数错误"
+		br.ErrMsg = fmt.Sprintf("用户参数错误 <%d>", sysUserId)
+		return
+	}
+	sysUser, err := models.GetSysUserById(sysUserId)
+	if err != nil {
+		br.Msg = "获取用户失败,用户已删除"
+		br.ErrMsg = "获取用户失败,用户已删除,Err" + err.Error()
+		return
+	}
+	sysUser.Password = ""
+	sysUser.SysDepartmentId = 0
+	br.Data = sysUser
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Edit
+// @Title 系统用户编辑
+// @Description 系统用户编辑
+// @Param	request	body UserLoginReq true "type json string"
+// @Success 200 {object} models.LoginResp
+// @router /edit [post]
+func (this *SysUserController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.SysUserInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SysUserName == "" {
+		br.Msg = "请输入用户名"
+		return
+	}
+	if req.SysUserId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = fmt.Sprintf("参数错误,sysUserId<%d>", req.SysUserId)
+		return
+	}
+	if req.SysRoleId <= 0 {
+		br.Msg = "请选择角色"
+		br.ErrMsg = "角色ID小于等于0"
+		return
+	}
+
+	sysUser, err := models.GetSysUserById(req.SysUserId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "用户不存在,请刷新页面"
+			return
+		}
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取用户数据失败,Err:" + err.Error() + ";sysUserId:" + fmt.Sprint(req.SysUserId)
+		return
+	}
+	item, err := models.GetSysUserBySysUserName(req.SysUserName)
+	if err != nil && err != orm.ErrNoRows {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取系统用户数据失败,Err:" + err.Error() + ";sysUserName:" + req.SysUserName
+		return
+	}
+	if item != nil && item.SysUserId != req.SysUserId {
+		br.Msg = "账号名称已存在,请重新输入"
+		return
+	}
+	req.Phone = strings.TrimSpace(req.Phone)
+	req.Email = strings.TrimSpace(req.Email)
+	if req.Phone == "" && req.Email == "" {
+		br.Msg = "至少输入一个手机号或邮箱"
+		return
+	}
+	if req.Phone != "" {
+		if req.AreaCode == "86" {
+			if !utils.ValidateMobileFormatat(req.Phone) {
+				br.Msg = "手机号格式有误, 请检查"
+				return
+			}
+		}
+	}
+	if req.Email != "" {
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "邮箱格式有误, 请检查"
+			return
+		}
+	}
+
+	var roleName string
+	roleItem, err := models.GetSysRoleById(req.SysRoleId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "角色不存在,请重新选择"
+			br.ErrMsg = "角色不存在"
+			return
+		}
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取角色数据失败,Err:" + err.Error()
+		return
+	}
+	if roleItem != nil {
+		roleName = roleItem.SysRoleName
+	}
+
+	sysUser.SysUserName = req.SysUserName
+	sysUser.SysRealName = req.RealName
+	sysUser.AreaCode = req.AreaCode
+	sysUser.Phone = req.Phone
+	sysUser.Email = req.Email
+	sysUser.SysRoleId = req.SysRoleId
+	sysUser.SysRoleName = roleName
+	sysUser.Province = req.Province
+	sysUser.City = req.City
+	sysUser.IsEnabled = req.IsEnabled
+	sysUser.ModifyTime = time.Now()
+	err = sysUser.Save()
+	if err != nil {
+		br.Msg = "编辑角色失败"
+		br.ErrMsg = "编辑角色失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "编辑成功"
+}
+
+// List
+// @Title 系统用户列表
+// @Description 系统用户列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   DepartmentId   query   int  true       "部门ID"
+// @Param   RoleId   query   int  true       "角色ID"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Success 200 {object} models.LoginResp
+// @router /list [get]
+func (this *SysUserController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	roleId, _ := this.GetInt("RoleId")
+	departmentId, _ := this.GetInt("DepartmentId")
+	keyWord := this.GetString("KeyWord")
+
+	var condition string
+	var pars []interface{}
+
+	if roleId > 0 {
+		condition += ` AND sys_role_id=? `
+		pars = append(pars, roleId)
+	}
+
+	if departmentId > 0 {
+		condition += ` AND (sys_department_id1=? OR sys_department_id2=? OR sys_department_id3=?) `
+		pars = append(pars, departmentId, departmentId, departmentId)
+	}
+
+	if keyWord != "" {
+		condition += ` AND (sys_real_name LIKE ? OR sys_user_name LIKE ? OR phone LIKE ? OR email LIKE ?) `
+		pars = utils.GetLikeKeywordPars(pars, keyWord, 4)
+	}
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	} else if pageSize > utils.PageSize100 {
+		pageSize = utils.PageSize100
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	total, err := models.GetSysUserCount(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	list, err := services.GetSysUserList(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	sysUserList := make([]models.SysUserView, len(list))
+	for i, user := range list {
+		sysUserList[i].SysUserId = user.SysUserId
+		sysUserList[i].SysUserName = user.SysUserName
+		sysUserList[i].SysRealName = user.SysRealName
+		sysUserList[i].Email = user.Email
+		sysUserList[i].Phone = user.Phone
+		sysUserList[i].AreaCode = user.AreaCode
+		sysUserList[i].SysRoleId = user.SysRoleId
+		sysUserList[i].SysRoleName = user.SysRoleName
+		sysUserList[i].SysDepartmentId = user.SysDepartmentId
+		var path string
+		if user.SysDepartmentName1 != "" {
+			path = user.SysDepartmentName1
+		}
+		if user.SysDepartmentName2 != "" {
+			path += "/" + user.SysDepartmentName2
+		}
+		if user.SysDepartmentName3 != "" {
+			path += "/" + user.SysDepartmentName3
+		}
+		sysUserList[i].SysDepartmentName = path
+		sysUserList[i].SysRoleId = user.SysRoleId
+		sysUserList[i].Province = user.Province
+		sysUserList[i].City = user.City
+		sysUserList[i].IsEnabled = user.IsEnabled
+		sysUserList[i].CreateTime = user.CreateTime
+		sysUserList[i].ModifyTime = user.ModifyTime
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	resp := new(response.SysUserListResp)
+	resp.List = sysUserList
+	resp.Paging = page
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ResetPass
+// @Title 重置密码
+// @Description 重置密码
+// @Param	request	body system.SysUserResetPassReq true "type json string"
+// @Success 200 编辑成功
+// @router /reset_pass [post]
+func (this *SysUserController) ResetPass() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.ResetPasswordReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SysUserId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数有误,SysUserNameId"
+		return
+	}
+	req.Password = strings.TrimSpace(req.Password)
+	req.RePassword = strings.TrimSpace(req.RePassword)
+	if req.Password == "" {
+		br.Msg = "密码不能为空"
+		return
+	}
+	if req.Password != req.RePassword {
+		br.Msg = "两次密码输入不一致"
+		return
+	}
+	sysUser, err := models.GetSysUserById(req.SysUserId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "用户已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取系统用户数据失败,Err:" + err.Error() + ";SysUserId:" + fmt.Sprint(req.SysUserId)
+		return
+	}
+	b, err := base64.StdEncoding.DecodeString(req.Password)
+	if err != nil {
+		br.Msg = "解析数据失败"
+		br.ErrMsg = "解析数据失败,Err:" + err.Error()
+		return
+	}
+	pwd := string(b)
+	if !utils.CheckPwd(pwd) {
+		br.Msg = "密码格式不对,必须包含8位及以上,包含数字、大写字母、小写字母、特殊字符中的三个类型"
+		return
+	}
+	pwd = utils.MD5(pwd)
+	sysUser.Password = pwd
+	sysUser.IsEnabled = true
+	sysUser.ModifyTime = time.Now()
+	err = sysUser.Save()
+	if err != nil {
+		br.Msg = "密码修改失败"
+		br.ErrMsg = "密码修改失败,系统错误,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Msg = "重置密码成功"
+	br.Success = true
+}
+
+// ResetMyPass
+// @Title 重置密码
+// @Description 重置密码
+// @Param	request	body system.SysUserResetPassReq true "type json string"
+// @Success 200 编辑成功
+// @router /reset_my_pass [post]
+func (this *SysUserController) ResetMyPass() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.ResetMyPasswordReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.Password != req.RePassword {
+		br.Msg = "两次密码输入不一致"
+		return
+	}
+
+	sysUser := this.SysUser
+	// 校验系统用户的密码
+	var pwd string
+	{
+		pwdByte, err := base64.StdEncoding.DecodeString(req.Password)
+		if err != nil {
+			br.Msg = "解析数据失败"
+			br.ErrMsg = "解析数据失败,Err:" + err.Error()
+			return
+		}
+		originPwd := string(pwdByte)
+		if !utils.CheckPwd(originPwd) {
+			br.Msg = "密码格式错误,请重新输入"
+			return
+		}
+		pwd = utils.MD5(originPwd)
+
+		originPwdByte, err := base64.StdEncoding.DecodeString(req.OriginPassword)
+		if err != nil {
+			br.Msg = "解析数据失败"
+			br.ErrMsg = "解析数据失败,Err:" + err.Error()
+			return
+		}
+		stringPwd := string(originPwdByte)
+		if sysUser.Password != utils.MD5(stringPwd) {
+			br.Msg = "原密码错误"
+			return
+		}
+	}
+
+	sysUser.Password = pwd
+	sysUser.ModifyTime = time.Now()
+	err = sysUser.Update([]string{"password", "modify_time"})
+	if err != nil {
+		br.Msg = "密码修改失败"
+		br.ErrMsg = "密码修改失败,系统错误,Err:" + err.Error()
+		return
+	}
+	//将用户对应的token给过期
+	services.LogoutSysUser(sysUser.SysUserId)
+
+	br.Msg = "重置密码成功"
+	br.Success = true
+	br.Ret = 200
+}
+
+// @Title 开启/禁用系统用户
+// @Description 开启/禁用系统用户接口
+// @Param	request	body system.SysuserEditReq true "type json string"
+// @Success 200 操作成功
+// @router /editEnabled [post]
+func (this *SysUserController) EditEnabled() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.SysUserEditEnabledReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	sysUser, err := models.GetSysUserById(req.SysUserId)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取系统用户数据失败,Err:" + err.Error()
+		return
+	}
+	if sysUser.SysRoleName == "admin" && sysUser.SysUserName == "admin" {
+		br.Msg = "禁止对admin使用<禁用>功能"
+		return
+	}
+	// 修改系统用户禁用状态
+	sysUser.IsEnabled = req.IsEnabled
+	err = sysUser.Update([]string{"is_enabled"})
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改系统用户数据失败,Err:" + err.Error()
+		return
+	}
+
+	//用户被禁用的情况下,需要将他对应的token给过期
+	if sysUser.IsEnabled && !req.IsEnabled {
+		services.LogoutSysUser(req.SysUserId)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// MoveToDepartment
+// @Title 移动分组
+// @Description 移动分组
+// @Param	request	body system.SysUserMoveReq true "type json string"
+// @Success 200 编辑成功
+// @router /moveToDepartment [post]
+func (this *SysUserController) MoveToDepartment() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.MoveToDepartmentReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	count, err := models.GetSysUserCountById(req.SysUserId)
+	if err != nil {
+		br.Msg = "移动分组失败,系统错误"
+		br.ErrMsg = "移动分组失败,系统错误,Err:" + err.Error()
+		return
+	}
+	if count <= 0 {
+		br.Msg = "用户已被删除, 请刷新页面"
+		return
+	}
+	count, err = models.GetSysDepartmentCountById(req.SysDepartmentId)
+	if err != nil {
+		br.Msg = "移动分组失败,系统错误"
+		br.ErrMsg = "移动分组失败,系统错误,Err:" + err.Error()
+		return
+	}
+	if count <= 0 {
+		br.Msg = "分组已被删除, 请刷新页面"
+		return
+	}
+	depPathIds, err := services.GetSysDepartmentPathIdsById(req.SysDepartmentId)
+	if err != nil {
+		br.Msg = "移动分组失败,系统错误"
+		br.ErrMsg = "获得分组路径失败,系统错误,Err:" + err.Error()
+		return
+	}
+	sysUser := &models.SysUser{}
+	sysUser.SysUserId = req.SysUserId
+	sysUser.SysDepartmentId1 = depPathIds[0]
+	sysUser.SysDepartmentId2 = depPathIds[1]
+	sysUser.SysDepartmentId3 = depPathIds[2]
+	sysUser.ModifyTime = time.Now()
+	if depPathIds[0] != 0 {
+		sysUser.SysDepartmentId = depPathIds[0]
+	}
+	if depPathIds[1] != 0 {
+		sysUser.SysDepartmentId = depPathIds[1]
+	}
+	if depPathIds[2] != 0 {
+		sysUser.SysDepartmentId = depPathIds[2]
+	}
+	err = sysUser.Update([]string{"sys_department_id", "sys_department_id1", "sys_department_id2", "sys_department_id3", "modify_time"})
+
+	// userDepMapping := &models.SysUserDepartmentMapping{}
+	// userDepMapping.SysUserId = req.SysUserId
+	// userDepMapping.SysDepartmentId1 = depPathIds[0]
+	// userDepMapping.SysDepartmentId2 = depPathIds[1]
+	// userDepMapping.SysDepartmentId3 = depPathIds[2]
+	// err = userDepMapping.Save()
+	if err != nil {
+		br.Msg = "移动分组失败,系统错误"
+		br.ErrMsg = "移动分组失败,系统错误,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "移动分组成功"
+}

+ 1169 - 0
controllers/user.go

@@ -0,0 +1,1169 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/models/request"
+	"eta/eta_mini_crm/models/response"
+	"eta/eta_mini_crm/services"
+	"eta/eta_mini_crm/utils"
+	"math"
+	"strings"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type UserController struct {
+	BaseAuthController
+}
+
+// Add
+// @Title 添加新客户
+// @Description 添加新客户
+// @Param   request	body request.UserAddReq true "type json string"
+// @Success 200 {object} request.UserAddReq
+// @router /add [post]
+func (this *UserController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.UserAddReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.RealName == "" {
+		br.Msg = "请输入姓名"
+		return
+	}
+	req.Phone = strings.TrimSpace(req.Phone)
+	req.Email = strings.TrimSpace(req.Email)
+	if req.Phone == "" && req.Email == "" {
+		br.Msg = "至少输入一个手机号或邮箱"
+		return
+	}
+
+	var userByPhone, userByEmail *models.User
+	if req.Phone != "" {
+		if req.AreaCode == "86" {
+			if !utils.ValidateMobileFormatat(req.Phone) {
+				br.Msg = "手机号格式有误, 请检查"
+				return
+			}
+		}
+		userByPhone, err = models.GetUserByPhone(req.Phone, req.AreaCode)
+		if err != nil && err != orm.ErrNoRows {
+			br.Msg = "添加用户失败"
+			br.ErrMsg = "查询用户失败,系统错误,Err:" + err.Error()
+			return
+		}
+		if userByPhone != nil {
+			switch userByPhone.Status {
+			case 0:
+				userByPhone.Status = 2
+			case 1:
+				userByPhone.Status = 2
+			default:
+				br.Msg = "手机号已存在,请重新输入"
+				return
+			}
+		}
+	}
+	if req.Email != "" {
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "邮箱格式有误, 请检查"
+			return
+		}
+		userByEmail, err = models.GetUserByEmail(req.Email)
+		if err != nil && err != orm.ErrNoRows {
+			br.Msg = "添加用户失败"
+			br.ErrMsg = "查询用户失败,系统错误,Err:" + err.Error()
+			return
+		}
+		if userByEmail != nil {
+			switch userByEmail.Status {
+			case 0:
+				userByEmail.Status = 2
+			case 1:
+				userByEmail.Status = 2
+			default:
+				br.Msg = "邮箱已存在,请重新输入"
+				return
+			}
+		}
+	}
+	if req.SellerId <= 0 {
+		br.Msg = "请选择营业部/销售"
+		return
+	}
+	if req.ValidStartTime == "" || req.ValidEndTime == "" {
+		br.Msg = "请选择合理的有效期范围"
+		return
+	}
+
+	validStartTime, err := time.Parse(utils.FormatDate, req.ValidStartTime)
+	if err != nil {
+		br.Msg = "错误的日期格式"
+		return
+	}
+	validEndTime, err := time.Parse(utils.FormatDate, req.ValidEndTime)
+	if err != nil {
+		br.Msg = "错误的日期格式"
+		return
+	}
+	if !validStartTime.Before(validEndTime) {
+		br.Msg = "请选择合理的有效期范围"
+		return
+	}
+	if req.Company == "" {
+		br.Msg = "请输入所属公司"
+		return
+	}
+	if userByPhone != nil && userByEmail != nil && userByPhone.UserId != userByEmail.UserId {
+		br.Msg = "邮箱已存在,请重新输入"
+		return
+	}
+
+	user := &models.User{}
+	curTime := time.Now()
+	if userByPhone == nil && userByEmail == nil {
+		user.CreateTime = curTime
+		user.ModifyTime = curTime
+	}
+	if userByEmail != nil {
+		user = userByEmail
+	}
+	if userByPhone != nil {
+		user = userByPhone
+	}
+	validStartTime = validStartTime.Local().Add(-time.Hour * 8)
+	validEndTime = validEndTime.Local().Add(-time.Hour*8 + time.Hour*24 - time.Second)
+	if curTime.Before(validEndTime) {
+		user.Status = 2
+	} else {
+		user.Status = 0
+	}
+
+	user.RealName = req.RealName
+	user.AreaCode = req.AreaCode
+	user.Phone = req.Phone
+	user.Email = req.Email
+	user.SellerId = req.SellerId
+	user.ValidStartTime = validStartTime
+	user.ValidEndTime = validEndTime
+	user.Company = req.Company
+	user.ModifyTime = curTime
+	err = models.SaveUser(user, req.ChartPermission)
+	if err != nil {
+		br.Msg = "添加客户失败"
+		br.ErrMsg = "添加客户失败,系统错误,Err:" + err.Error()
+		return
+	}
+
+	if user.Status == 2 {
+		userRecord := &models.UserChangeRecord{}
+		userRecord.UserId = user.UserId
+		userRecord.SysUserId = this.SysUser.SysUserId
+		userRecord.Content = this.SysUser.SysRealName + "新增用户"
+		userRecord.Insert()
+	}
+
+	br.Msg = "添加成功"
+	br.Success = true
+	br.Ret = 200
+
+}
+
+// edit
+// @Title 编辑客户
+// @Description 编辑客户
+// @Param   request	body request.UserEidtReq true "type json string"
+// @Success 200 {object} request.UserAddReq
+// @router /edit [post]
+func (this *UserController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.UserEidtReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.RealName == "" {
+		br.Msg = "请输入姓名"
+		return
+	}
+	req.Phone = strings.TrimSpace(req.Phone)
+	req.Email = strings.TrimSpace(req.Email)
+	if req.Phone == "" && req.Email == "" {
+		br.Msg = "至少输入一个手机号或邮箱"
+		return
+	}
+	if req.Phone != "" {
+		if req.AreaCode == "86" {
+			if !utils.ValidateMobileFormatat(req.Phone) {
+				br.Msg = "手机号格式有误, 请检查"
+				return
+			}
+		}
+		user, err := models.GetUserByPhone(req.Phone, req.AreaCode)
+		if err != nil && err != orm.ErrNoRows {
+			br.Msg = "编辑用户失败"
+			br.ErrMsg = "查询用户失败,系统错误,Err:" + err.Error()
+			return
+		}
+		if user != nil && user.UserId != req.UserId {
+			br.Msg = "手机号已存在,请重新输入"
+			return
+		}
+	}
+	if req.Email != "" {
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "邮箱格式有误, 请检查"
+			return
+		}
+
+		user, err := models.GetUserByEmail(req.Email)
+		if err != nil && err != orm.ErrNoRows {
+			br.Msg = "编辑用户失败"
+			br.ErrMsg = "查询用户失败,系统错误,Err:" + err.Error()
+			return
+		}
+		if user != nil && user.UserId != req.UserId {
+			br.Msg = "邮箱已存在,请重新输入"
+			return
+		}
+	}
+	if req.SellerId <= 0 {
+		br.Msg = "请选择营业部/销售"
+		return
+	}
+	if req.ValidStartTime == "" || req.ValidEndTime == "" {
+		br.Msg = "请选择合理的有效期范围"
+		return
+	}
+
+	validStartTime, err := time.Parse(utils.FormatDate, req.ValidStartTime)
+	if err != nil {
+		br.Msg = "错误的日期格式"
+		return
+	}
+	validStartTime = validStartTime.In(time.Local).Add(-time.Hour * 8)
+	validEndTime, err := time.Parse(utils.FormatDate, req.ValidEndTime)
+	if err != nil {
+		br.Msg = "错误的日期格式"
+		return
+	}
+	validEndTime = validEndTime.In(time.Local).Add(-time.Hour*8 + time.Hour*24 - time.Second)
+	if !validStartTime.Before(validEndTime) {
+		br.Msg = "请选择合理的有效期范围"
+		return
+	}
+	if req.Company == "" {
+		br.Msg = "请输入所属公司"
+		return
+	}
+
+	if req.IsEnabled && time.Now().After(validEndTime) {
+		br.Msg = "启用后,有效期必须大于当前时间"
+		return
+	}
+
+	user, err := models.GetUserById(req.UserId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "用户不存在或已删除,请重新刷新页面"
+			br.ErrMsg = "用户不存在或已删除,请重新刷新页面,Err:" + err.Error()
+			return
+		}
+		br.Msg = "编辑用户失败"
+		br.ErrMsg = "编辑用户失败, 系统错误,Err:" + err.Error()
+		return
+	}
+	user.RealName = req.RealName
+	user.AreaCode = req.AreaCode
+	user.Phone = req.Phone
+	user.Email = req.Email
+	user.SellerId = req.SellerId
+	user.ValidStartTime = validStartTime
+	user.ValidEndTime = validEndTime
+	user.Company = req.Company
+	user.ModifyTime = time.Now()
+	// 当用户状态初始为潜在客户时,才更新创建时间
+	if user.Status == utils.UserStatusPotential {
+		user.CreateTime = time.Now()
+	}
+	oldStatus := user.Status
+	if !req.IsEnabled {
+		user.Status = 0
+	} else {
+		user.Status = 2
+	}
+	err = models.SaveUser(user, req.ChartPermission)
+	if err != nil {
+		br.Msg = "添加客户失败"
+		br.ErrMsg = "添加客户失败,系统错误,Err:" + err.Error()
+		return
+	}
+	record := &models.UserChangeRecord{}
+	record.UserId = req.UserId
+	record.SysUserId = this.SysUser.SysUserId
+	if oldStatus == utils.UserStatusPotential && user.Status == utils.UserStatusFormal {
+		record.Content = this.SysUser.SysRealName + "新增用户"
+	} else {
+		record.Content = this.SysUser.SysRealName + "编辑用户"
+	}
+	record.Insert()
+
+	br.Msg = "编辑成功"
+	br.Success = true
+	br.Ret = 200
+}
+
+// Check
+// @Title 编辑客户
+// @Description 编辑客户
+// @Param   request	body request.UserEidtReq true "type json string"
+// @Success 200 {object} request.UserAddReq
+// @router /check [post]
+func (this *UserController) Check() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.UserCheckReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	req.Phone = strings.TrimSpace(req.Phone)
+	req.Email = strings.TrimSpace(req.Email)
+	if req.Phone == "" && req.Email == "" {
+		br.Msg = "至少输入一个手机号或邮箱"
+		return
+	}
+	userCheckResp := new(response.UserCheckResp)
+
+	if req.Phone != "" {
+		if req.AreaCode == "86" {
+			if !utils.ValidateMobileFormatat(req.Phone) {
+				br.Msg = "手机号格式有误, 请检查"
+				return
+			}
+		}
+		user, err := models.GetUserByPhone(req.Phone, req.AreaCode)
+		if err != nil && err != orm.ErrNoRows {
+			br.Msg = "编辑用户失败"
+			br.ErrMsg = "查询用户失败,系统错误,Err:" + err.Error()
+			return
+		}
+		if user != nil {
+			userCheckResp.UserId = user.UserId
+			userCheckResp.Status = user.Status
+			if user.Status == utils.UserStatusNo {
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "该用户已被禁用,确认启用并更新用户信息吗?"
+				br.Data = userCheckResp
+				return
+			}
+			if user.Status == utils.UserStatusPotential {
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "该用户已在潜在列表,确认转客户并更新用户信息吗?"
+				br.Data = userCheckResp
+				return
+			}
+			if user.Status == utils.UserStatusFormal {
+				br.Msg = "手机号已存在,请重新输入"
+				return
+			}
+		}
+
+	}
+	if req.Email != "" {
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "邮箱格式有误, 请检查"
+			return
+		}
+
+		user, err := models.GetUserByEmail(req.Email)
+		if err != nil && err != orm.ErrNoRows {
+			br.Msg = "编辑用户失败"
+			br.ErrMsg = "查询用户失败,系统错误,Err:" + err.Error()
+			return
+		}
+		if user != nil {
+			userCheckResp.UserId = user.UserId
+			if user.Status == 0 {
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "该用户已被禁用,确认启用并更新用户信息吗?"
+				br.Data = userCheckResp
+				return
+			}
+			if user.Status == 1 {
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "该用户已被禁用,确认启用并更新用户信息吗?"
+				br.Data = userCheckResp
+				return
+			}
+			if user.Status == 2 {
+				br.Msg = "邮箱已存在,请重新输入"
+				return
+			}
+			return
+		}
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "邮箱或手机号合格"
+}
+
+// List
+// @Title 用户列表
+// @Description 用户列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   SellerId   query   string  true       "销售id"
+// @Param   Status   query   int  true       "用户状态"
+// @Param   KeyWord   query   string  true       "手机号/邮箱/姓名"
+// @Param   IsRegistered   query   string  true       "是否注册"
+// @Param   IsSubscribed   query   string  true       "是否关注"
+// @Param   RegisterStartDate   query   string  true       "注册开始时间"
+// @Param   RegisterEndDate   query   string  true       "注册结束时间"
+// @Param   CreateStartDate   query   string  true       "创建开始时间"
+// @Param   CreateEndDate   query   string  true       "创建结束时间"
+// @Param   SortParam   query   string  true       "排序字段"
+// @Param   SortType   query   string  true       "排序方式"
+// @Success 200 {object} response.UserListResp
+// @router /list [get]
+func (this *UserController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	sellerIdStr := this.GetString("SellerId")
+	status := this.GetString("Status")
+	keyWord := this.GetString("KeyWord")
+	IsRegistered := this.GetString("IsRegistered")
+	IsSubscribed := this.GetString("IsSubscribed")
+	registerStartDate := this.GetString("RegisterStartDate")
+	registerEndDate := this.GetString("RegisterEndDate")
+	createStartDate := this.GetString("CreateStartDate")
+	createEndDate := this.GetString("CreateEndDate")
+	sortParma := this.GetString("SortParam")
+	sortType := this.GetString("SortType")
+
+	var condition string
+	var sortCondition string
+	var pars []interface{}
+
+	if keyWord != "" {
+		condition += ` AND (u.real_name LIKE ? OR u.phone LIKE ? OR u.email LIKE ?) `
+		pars = utils.GetLikeKeywordPars(pars, keyWord, 3)
+	}
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	} else if pageSize > utils.PageSize100 {
+		pageSize = utils.PageSize100
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+
+	if sortParma != "" && sortType != "" {
+		sortCondition = " ORDER BY "
+		var param, sort string
+		switch sortParma {
+		case "RegisterTime":
+			param = "u.register_time"
+		case "CreateTime":
+			param = "u.create_time"
+		case "RestDate":
+			param = "u.valid_end_time"
+		}
+		switch sortType {
+		case "asc":
+			sort = " ASC "
+		case "desc":
+			sort = " DESC "
+		}
+		if param != "" && sort != "" {
+			sortCondition += param + " " + sort
+		} else {
+			sortCondition = ""
+		}
+	}
+	if sortCondition == "" {
+		sortCondition = " ORDER BY u.valid_end_time ASC"
+	}
+
+	if sellerIdStr != "" {
+		sellerIds := strings.Split(sellerIdStr, ",")
+		if len(sellerIds) != 0 {
+			condition += ` AND ( `
+			for i, id := range sellerIds {
+				if i == 0 {
+					condition += ` u.seller_id = ? `
+					pars = append(pars, id)
+				} else {
+					condition += ` OR u.seller_id = ? `
+					pars = append(pars, id)
+				}
+			}
+			condition += `) `
+		}
+	}
+	switch status {
+	case "禁用":
+		condition += " AND u.status=? "
+		pars = append(pars, 0)
+	case "潜在":
+		condition += " AND u.status=? "
+		pars = append(pars, 1)
+	case "正式":
+		condition += " AND u.status=? "
+		pars = append(pars, 2)
+	case "":
+		condition += " AND (u.status=? OR u.status=?) "
+		pars = append(pars, 0, 2)
+	}
+	switch IsRegistered {
+	case "是":
+		condition += " AND u.is_registered=? "
+		pars = append(pars, true)
+	case "否":
+		condition += " AND u.is_registered=? "
+		pars = append(pars, false)
+	}
+	switch IsSubscribed {
+	case "是":
+		condition += " AND u.is_subscribed=? "
+		pars = append(pars, true)
+	case "否":
+		condition += " AND u.is_subscribed=? "
+		pars = append(pars, false)
+	}
+	if registerStartDate != "" {
+		registerStartTime, er := time.Parse(utils.FormatDate, registerStartDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		condition += " AND u.register_time>=? "
+		registerStartDateStr := registerStartTime.Format(utils.FormatDateTime)
+		pars = append(pars, registerStartDateStr)
+	}
+	if registerEndDate != "" {
+		registerEndTime, er := time.Parse(utils.FormatDate, registerEndDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		condition += " AND u.register_time<=? "
+		// 结束时间包含今天
+		registerEndTime = registerEndTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
+		registerEndDateStr := registerEndTime.Format(utils.FormatDateTime)
+		pars = append(pars, registerEndDateStr)
+	}
+	if createStartDate != "" {
+		createStartTime, er := time.Parse(utils.FormatDate, createStartDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		condition += " AND u.create_time>=? "
+		createStartDateStr := createStartTime.Format(utils.FormatDateTime)
+		pars = append(pars, createStartDateStr)
+	}
+	if createEndDate != "" {
+		createEndTime, er := time.Parse(utils.FormatDate, createEndDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		condition += " AND u.create_time<=? "
+		// 结束时间包含今天
+		createEndTime = createEndTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
+		createEndDateStr := createEndTime.Format(utils.FormatDateTime)
+		pars = append(pars, createEndDateStr)
+	}
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	} else if pageSize > utils.PageSize100 {
+		pageSize = utils.PageSize100
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize := utils.StartIndex(currentIndex, pageSize)
+
+	total, err := models.GetUserCount(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	userList, err := models.GetUserListByConditonSort(condition, sortCondition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "查询用户失败"
+		br.Msg = "查询用户失败,系统错误,Err:" + err.Error()
+		return
+	}
+	for _, u := range userList {
+		if u.ValidEndTime == "" {
+			u.RestDate = 0
+		} else {
+			endTime, err := time.Parse(utils.FormatDateTime, u.ValidEndTime)
+			if err != nil {
+				br.Msg = "用户有效时间格式有误"
+				br.ErrMsg = "用户有效时间格式有误,Err:" + err.Error()
+				return
+			}
+			// 对天数向上取整
+			daysBetween := int(math.Ceil(time.Until(endTime).Hours() / 24))
+			if daysBetween < 0 {
+				daysBetween = 0
+			}
+			u.RestDate = daysBetween
+		}
+	}
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(response.UserListResp)
+	resp.Paging = page
+	resp.List = userList
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// PotentialList
+// @Title 潜在用户列表
+// @Description 潜在用户列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "手机号/邮箱"
+// @Param   RegisterStartDate   query   string  true       "注册开始时间"
+// @Param   RegisterEndDate   query   string  true       "注册结束时间"
+// @Param   LastUpdateStartDate query   string  true       "最后一次阅读开始时间"
+// @Param   LastUpdateEndDate   query   string  true       "最后一次阅读结束时间"
+// @Param   SortParam   query   string  true       "排序字段"
+// @Param   SortType   query   string  true       "排序方式"
+// @Success 200 {object} response.UserListResp
+// @router /potential/list [get]
+func (this *UserController) PotentialList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	keyWord := this.GetString("KeyWord")
+	registerStartDate := this.GetString("RegisterStartDate")
+	registerEndDate := this.GetString("RegisterEndDate")
+	lastUpdateStartDate := this.GetString("LastUpdateStartDate")
+	lastUpdateEndDate := this.GetString("LastUpdateEndDate")
+	sortParma := this.GetString("SortParam")
+	sortType := this.GetString("SortType")
+
+	var sortCondition string
+	var condition string
+	var pars []interface{}
+
+	if keyWord != "" {
+		condition += ` AND ( u.phone LIKE ? OR u.email LIKE ?) `
+		pars = utils.GetLikeKeywordPars(pars, keyWord, 2)
+	}
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	} else if pageSize > utils.PageSize100 {
+		pageSize = utils.PageSize100
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+
+	if sortParma != "" && sortType != "" {
+		sortCondition = " ORDER BY "
+		var param, sort string
+		switch sortParma {
+		case "RegisterTime":
+			param = "u.register_time"
+		case "LastUpdateTime":
+			param = "last_update_time"
+		case "ReadCnt":
+			param = "read_cnt"
+		}
+		switch sortType {
+		case "asc":
+			sort = " ASC "
+		case "desc":
+			sort = " DESC "
+		}
+		if param != "" && sort != "" {
+			sortCondition += param + " " + sort
+		} else {
+			sortCondition = ""
+		}
+	}
+
+	if registerStartDate != "" {
+		registerStartTime, er := time.Parse(utils.FormatDate, registerStartDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		condition += " AND u.register_time>=? "
+		registerStartDateStr := registerStartTime.Format(utils.FormatDateTime)
+		pars = append(pars, registerStartDateStr)
+	}
+	if registerEndDate != "" {
+		registerEndTime, er := time.Parse(utils.FormatDate, registerEndDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		condition += " AND u.register_time<=? "
+		// 结束时间包含今天
+		registerEndTime = registerEndTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
+		registerEndDateStr := registerEndTime.Format(utils.FormatDateTime)
+		pars = append(pars, registerEndDateStr)
+	}
+	if lastUpdateStartDate != "" {
+		lastUpdateStartTime, er := time.Parse(utils.FormatDate, lastUpdateStartDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		condition += " AND ur.create_time>=? "
+
+		lastUpdateStartDateStr := lastUpdateStartTime.Format(utils.FormatDateTime)
+		pars = append(pars, lastUpdateStartDateStr)
+	}
+	if lastUpdateEndDate != "" {
+		lastUpdateEndTime, er := time.Parse(utils.FormatDate, lastUpdateEndDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		// 结束时间包含今天
+		lastUpdateEndTime = lastUpdateEndTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
+		lastUpdateEndDateStr := lastUpdateEndTime.Format(utils.FormatDateTime)
+		condition += " AND ur.create_time<=? "
+		pars = append(pars, lastUpdateEndDateStr)
+	}
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	} else if pageSize > utils.PageSize100 {
+		pageSize = utils.PageSize100
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize := utils.StartIndex(currentIndex, pageSize)
+
+	total, err := models.GetPotentialUserCountByConditonV2(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	resp := new(response.UserListResp)
+	if total == 0 {
+		page := paging.GetPaging(currentIndex, pageSize, total)
+		resp.Paging = page
+		br.Msg = "获取成功"
+		br.Ret = 200
+		br.Success = true
+		br.Data = resp
+		return
+	}
+	userList, err := models.GetPotentialUserIdsByConditonV2(condition, pars, sortCondition, startSize, pageSize)
+	if err != nil {
+		br.Msg = "查询用户失败"
+		br.Msg = "查询用户失败,系统错误,Err:" + err.Error()
+		return
+	}
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp.Paging = page
+	resp.List = userList
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// PotentialEdit
+// @Title 编辑潜在客户(转客户)
+// @Description 编辑潜在客户(转客户)
+// @Param   request	body request.UserEidtReq true "type json string"
+// @Success 200 {object} request.UserAddReq
+// @router /potential/edit [post]
+func (this *UserController) PotentialEdit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.UserEidtReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.RealName == "" {
+		br.Msg = "请输入姓名"
+		return
+	}
+	req.Phone = strings.TrimSpace(req.Phone)
+	req.Email = strings.TrimSpace(req.Email)
+	if req.Phone == "" && req.Email == "" {
+		br.Msg = "至少输入一个手机号或邮箱"
+		return
+	}
+	if req.Phone != "" {
+		if req.AreaCode == "86" {
+			if !utils.ValidateMobileFormatat(req.Phone) {
+				br.Msg = "手机号格式有误, 请检查"
+				return
+			}
+		}
+		user, err := models.GetUserByPhone(req.Phone, req.AreaCode)
+		if err != nil && err != orm.ErrNoRows {
+			br.Msg = "编辑用户失败"
+			br.ErrMsg = "查询用户失败,系统错误,Err:" + err.Error()
+			return
+		}
+		if user != nil && user.UserId != req.UserId {
+			br.Msg = "手机号已存在,请重新输入"
+			return
+		}
+	}
+	if req.Email != "" {
+		if !utils.ValidateEmailFormatat(req.Email) {
+			br.Msg = "邮箱格式有误, 请检查"
+			return
+		}
+
+		user, err := models.GetUserByEmail(req.Email)
+		if err != nil && err != orm.ErrNoRows {
+			br.Msg = "编辑用户失败"
+			br.ErrMsg = "查询用户失败,系统错误,Err:" + err.Error()
+			return
+		}
+		if user != nil && user.UserId != req.UserId {
+			br.Msg = "邮箱已存在,请重新输入"
+			return
+		}
+	}
+	if req.SellerId <= 0 {
+		br.Msg = "请选择营业部/销售"
+		return
+	}
+	if req.ValidStartTime == "" || req.ValidEndTime == "" {
+		br.Msg = "请选择合理的有效期范围"
+		return
+	}
+
+	validStartTime, err := time.Parse(utils.FormatDate, req.ValidStartTime)
+	if err != nil {
+		br.Msg = "错误的日期格式"
+		return
+	}
+	validStartTime = validStartTime.In(time.Local).Add(-time.Hour * 8)
+	validEndTime, err := time.Parse(utils.FormatDate, req.ValidEndTime)
+	if err != nil {
+		br.Msg = "错误的日期格式"
+		return
+	}
+	validEndTime = validEndTime.In(time.Local).Add(-time.Hour*8 + time.Hour*24 - time.Second)
+	if !validStartTime.Before(validEndTime) {
+		br.Msg = "请选择合理的有效期范围"
+		return
+	}
+	if req.Company == "" {
+		br.Msg = "请输入所属公司"
+		return
+	}
+
+	if req.IsEnabled && time.Now().After(validEndTime) {
+		br.Msg = "启用后,有效期必须大于当前时间"
+		return
+	}
+
+	user, err := models.GetUserById(req.UserId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "用户不存在或已删除,请重新刷新页面"
+			br.ErrMsg = "用户不存在或已删除,请重新刷新页面,Err:" + err.Error()
+			return
+		}
+		br.Msg = "编辑用户失败"
+		br.ErrMsg = "编辑用户失败, 系统错误,Err:" + err.Error()
+		return
+	}
+	user.RealName = req.RealName
+	user.AreaCode = req.AreaCode
+	user.Phone = req.Phone
+	user.Email = req.Email
+	user.SellerId = req.SellerId
+	user.ValidStartTime = validStartTime
+	user.ValidEndTime = validEndTime
+	user.Company = req.Company
+	user.ModifyTime = time.Now()
+	// 当用户状态初始为潜在客户时,才更新创建时间
+	if user.Status == utils.UserStatusPotential {
+		user.CreateTime = time.Now()
+	}
+	oldStatus := user.Status
+	if !req.IsEnabled {
+		user.Status = 0
+	} else {
+		user.Status = 2
+	}
+	err = models.SaveUser(user, req.ChartPermission)
+	if err != nil {
+		br.Msg = "添加客户失败"
+		br.ErrMsg = "添加客户失败,系统错误,Err:" + err.Error()
+		return
+	}
+	record := &models.UserChangeRecord{}
+	record.UserId = req.UserId
+	record.SysUserId = this.SysUser.SysUserId
+	if oldStatus == utils.UserStatusPotential && user.Status == utils.UserStatusFormal {
+		record.Content = this.SysUser.SysRealName + "新增用户"
+	} else {
+		record.Content = this.SysUser.SysRealName + "编辑用户"
+	}
+	record.Insert()
+
+	br.Msg = "编辑成功"
+	br.Success = true
+	br.Ret = 200
+}
+
+// Detail
+// @Title 用户详情信息
+// @Description 用户详情信息
+// @Param   UserId   query   int  true       "用户id"
+// @Success 200 {object} models.LoginResp
+// @router /detail [get]
+func (this *UserController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	UserId, _ := this.GetInt("UserId")
+	if UserId <= 0 {
+		br.Msg = "查询用户不存在"
+		return
+	}
+	user, err := models.GetUserViewById(UserId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "用户不存在或已删除,请刷新页面"
+			return
+		}
+		br.Msg = "查询用户失败"
+		br.ErrMsg = "查询用户失败,系统错误,Err:" + err.Error()
+		return
+	}
+	if user == nil {
+		br.Msg = "用户不存在或已删除,请刷新页面"
+		return
+	}
+	permissionList, err := services.GetUserPermissionById(UserId)
+	if err != nil {
+		br.Msg = "用户权限获取失败,请重新尝试"
+		return
+	}
+	permissionMap := make(map[string][]string)
+	for _, pm := range permissionList {
+		permissionMap[pm.ParentName] = append(permissionMap[pm.ParentName], pm.PermissionName)
+	}
+	resp := new(response.UserDetailResp)
+	resp.Detail = user
+	resp.Permission = permissionMap
+
+	br.Msg = "查询成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+}
+
+// Delete
+// @Title 系统用户详情信息
+// @Description 用户详情信息
+// @Param   request	body request.UserDeleteReq true "type json string"
+// @Success 200 {object} models.LoginResp
+// @router /delete [post]
+func (this *UserController) Delete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.UserDeleteReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	err = models.DeleteUserById(req.UserId)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,系统错误,Err:" + err.Error()
+		return
+	}
+	br.Msg = "删除成功"
+	br.Success = true
+	br.Ret = 200
+}
+
+// @Title 开启/禁用用户
+// @Description 开启/禁用用户接口
+// @Param	request	body system.SysuserEditReq true "type json string"
+// @Success 200 操作成功
+// @router /editEnabled [post]
+func (this *UserController) EditEnabled() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.UserEditEnabledReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	user, err := models.GetUserById(req.UserId)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取系统用户数据失败,Err:" + err.Error()
+		return
+	}
+	if !req.IsEnabled {
+		user.Status = 0
+		err = user.Update([]string{"status"})
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "修改系统用户数据失败,Err:" + err.Error()
+			return
+		}
+	} else {
+		validStartTime, err := time.Parse(utils.FormatDate, req.ValidStartTime)
+		if err != nil {
+			br.Msg = "日期格式不正确"
+			return
+		}
+		validEndTime, err := time.Parse(utils.FormatDate, req.ValidEndTime)
+		if err != nil {
+			br.Msg = "日期格式不正确"
+			return
+		}
+		if !validEndTime.After(validStartTime) {
+			br.Msg = "有效期结束日期在开始日期之前,日期格式不正确"
+			return
+		}
+		validStartTime = validStartTime.Local().Add(-time.Hour * 8)
+		validEndTime = validEndTime.Local().Add(-time.Hour*8 + time.Hour*24 - time.Second)
+		curTime := time.Now()
+		if curTime.After(validEndTime) {
+			br.Msg = "有效期已到期,请重新设置"
+			return
+		}
+		user.Status = 2
+		user.ModifyTime = curTime
+		user.ValidStartTime = validStartTime
+		user.ValidEndTime = validEndTime
+		err = user.Update([]string{"status", "modify_time", "valid_start_time", "valid_end_time"})
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "修改系统用户数据失败,Err:" + err.Error()
+			return
+		}
+	}
+	// 记录操作
+	record := &models.UserChangeRecord{}
+	record.UserId = req.UserId
+	record.SysUserId = this.SysUser.SysUserId
+	if user.Status == 0 {
+		record.Content = this.SysUser.SysRealName + "禁用用户"
+	} else {
+		record.Content = this.SysUser.SysRealName + "启用用户"
+	}
+	record.Insert()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// @Title 用户信息变更记录
+// @Description 用户信息变更记录
+// @Param   UserId   query   int  true       "用户id"
+// @Success 200 操作成功
+// @router /change_list [get]
+func (this *UserController) ChangeList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	userId, _ := this.GetInt("UserId")
+	if userId <= 0 {
+		br.Msg = "用户信息错误"
+		return
+	}
+
+	userRcord, err := models.GetUserChangeRecordListById(userId)
+	if err != nil {
+		br.Msg = "用户信息变更查询失败"
+		br.ErrMsg = "用户信息变更查询失败,系统错误,Err:" + err.Error()
+		return
+	}
+	resp := new(response.UserChangeRecordResp)
+	resp.List = userRcord
+
+	br.Data = resp
+	br.Msg = "查询成功"
+	br.Success = true
+	br.Ret = 200
+}

+ 151 - 0
controllers/user_login.go

@@ -0,0 +1,151 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/models/request"
+	"eta/eta_mini_crm/models/response"
+	"eta/eta_mini_crm/utils"
+	"fmt"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type UserLoginController struct {
+	BaseCommonController
+}
+
+// Login
+// @Title 用户登录
+// @Description 用户登录
+// @Param	request	body UserLoginReq true "type json string"
+// @Success 200 {object} models.LoginResp
+// @router /login [post]
+func (this *UserLoginController) Login() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.UserLoginReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.UserName == "" {
+		br.Msg = "请输入账号"
+		return
+	}
+	if req.Password == "" {
+		br.Msg = "请输入密码"
+		return
+	}
+	sysUser, err := models.GetSysUserBySysUserName(req.UserName)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "登录失败, 账号或密码错误"
+			return
+		} else {
+			br.Msg = "系统错误"
+			br.ErrMsg = "系统错误" + err.Error()
+			return
+		}
+	}
+	sysRole, err := models.GetSysRoleById(sysUser.SysRoleId)
+	if err != nil {
+		br.Msg = "登录失败"
+		br.ErrMsg = "查询角色失败, Err:" + err.Error()
+		return
+	}
+	dbPass := utils.MD5(fmt.Sprintf("%s%s%s", sysUser.Password, utils.UserLoginSalt, req.ReqTime))
+	if req.Password != dbPass {
+		br.Msg = "登录失败, 账号或密码错误"
+		return
+	}
+	account := utils.MD5(sysUser.SysUserName)
+	token := utils.GenToken(account)
+	sysSession := new(models.SysSession)
+	sysSession.UserName = sysUser.SysUserName
+	sysSession.SysUserId = sysUser.SysUserId
+	sysSession.ExpiredTime = time.Now().AddDate(0, 0, 60)
+	sysSession.CreatedTime = time.Now()
+	sysSession.LastUpdatedTime = time.Now()
+	sysSession.AccessToken = token
+	err = sysSession.AddSysSession()
+	if err != nil {
+		br.Msg = "登录失败"
+		br.ErrMsg = "新增session信息失败, Err:" + err.Error()
+		return
+	}
+	resp := new(response.LoginResp)
+	resp.Authorization = "authorization=" + token
+	resp.SysUserName = sysUser.SysUserName
+	resp.SysRealName = sysUser.SysRealName
+	resp.SysUserId = sysUser.SysUserId
+	resp.RoleName = sysRole.SysRoleName
+	resp.RoleId = sysUser.SysRoleId
+
+	// 获取不可信的登录态,并将该登录态重置掉,不允许多次登录
+	noTrustLoginKey := fmt.Sprint(utils.CACHE_ACCESS_TOKEN_LOGIN_NO_TRUST, sysUser.SysUserId)
+	noTrustLoginId, _ := utils.Rc.RedisString(noTrustLoginKey)
+	fmt.Println("noTrustLoginId:", noTrustLoginId)
+	if noTrustLoginId != `` { // 如果存在不可信设备,那么将其下架
+		oldNoTrustLoginKey := fmt.Sprint(utils.CACHE_ACCESS_TOKEN_LOGIN, noTrustLoginId)
+		utils.Rc.Put(oldNoTrustLoginKey, "0", time.Hour*24)
+	}
+	// 设置redis缓存,记录用户登录态
+	loginKey := fmt.Sprint(utils.CACHE_ACCESS_TOKEN_LOGIN, sysSession.SysSessionId)
+	utils.Rc.Put(loginKey, "1", time.Hour*24)
+	utils.Rc.Put(noTrustLoginKey, sysSession.SysSessionId, time.Hour*24*60)
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "登录成功"
+}
+
+// AreaCodeList
+// @Title 手机号区号列表
+// @Description 手机号区号列表
+// @Success 200 Ret=200 获取成功
+// @router /area_code/list [get]
+func (this *UserLoginController) AreaCodeList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	resp := make([]response.AreaCodeListResp, 0)
+	confAuth, e := models.GetConfigDetailByCode(models.ConfAreaCodeListKey)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取手机号区号配置失败, Err: " + e.Error()
+		return
+	}
+	if confAuth.ConfigValue == "" {
+		br.Msg = "获取失败"
+		br.ErrMsg = "手机号区号配置为空"
+		return
+	}
+	if e := json.Unmarshal([]byte(confAuth.ConfigValue), &resp); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "手机号区号配置有误"
+		return
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 447 - 0
controllers/user_read_record.go

@@ -0,0 +1,447 @@
+package controllers
+
+import (
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/models/response"
+	"eta/eta_mini_crm/utils"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type UserReadRecordController struct {
+	BaseAuthController
+}
+
+// List
+// @Title 用户阅读统计列表
+// @Description 用户阅读统计列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   SellerId   query   int  true       "销售id"
+// @Param   Status   query   string  true       "用户状态"
+// @Param   KeyWord   query   string  true       "手机号/邮箱/姓名"
+// @Param   IsRegistered   query   string  true       "是否注册"
+// @Param   IsSubscribed   query   string  true       "是否关注"
+// @Param   RegisterStartDate   query   string  true       "注册开始时间"
+// @Param   RegisterEndDate   query   string  true       "注册结束时间"
+// @Param   CreateStartDate   query   string  true       "创建开始时间"
+// @Param   CreateEndDate   query   string  true       "创建结束时间"
+// @Param   SortParam   query   string  true       "排序字段"
+// @Param   SortType   query   string  true       "排序方式"
+// @Success 200 {object} response.UserListResp
+// @router /list [get]
+func (this *UserReadRecordController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	sellerIdStr := this.GetString("SellerId")
+	status := this.GetString("Status")
+	keyWord := this.GetString("KeyWord")
+	IsRegistered := this.GetString("IsRegisterd")
+	IsSubscribed := this.GetString("IsSubscribed")
+	registerStartDate := this.GetString("RegisterStartDate")
+	registerEndDate := this.GetString("RegisterEndDate")
+	createStartDate := this.GetString("CreateStartDate")
+	createEndDate := this.GetString("CreateEndDate")
+	sortParma := this.GetString("SortParam")
+	sortType := this.GetString("SortType")
+
+	var condition string
+	var sortCondition string
+	var pars []interface{}
+
+	if keyWord != "" {
+		condition += ` AND (u.real_name LIKE ? OR u.phone LIKE ? OR u.email LIKE ? OR u.company LIKE ?) `
+		pars = utils.GetLikeKeywordPars(pars, keyWord, 4)
+	}
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	} else if pageSize > utils.PageSize100 {
+		pageSize = utils.PageSize100
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+
+	if sellerIdStr != "" {
+		sellerIds := strings.Split(sellerIdStr, ",")
+		if len(sellerIds) != 0 {
+			condition += ` AND ( `
+			for i, id := range sellerIds {
+				if i == 0 {
+					condition += ` u.seller_id = ? `
+					pars = append(pars, id)
+				} else {
+					condition += ` OR u.seller_id = ? `
+					pars = append(pars, id)
+				}
+			}
+			condition += `) `
+		}
+	}
+	if sortParma != "" && sortType != "" {
+		sortCondition = " ORDER BY "
+		var param, sort string
+		switch sortParma {
+		case "LastUpdateTime":
+			param = "last_update_time"
+		case "ReadCnt":
+			param = "read_cnt"
+		}
+		switch sortType {
+		case "asc":
+			sort = " ASC "
+		case "desc":
+			sort = " DESC "
+		}
+		if param != "" && sort != "" {
+			sortCondition += param + " " + sort
+		} else {
+			sortCondition = ""
+		}
+	}
+
+	switch status {
+	case fmt.Sprint(utils.UserStatusNo):
+		condition += " AND u.status=? "
+		pars = append(pars, 0)
+	case fmt.Sprint(1):
+		condition += " AND u.status=? "
+		pars = append(pars, 2)
+	default:
+		condition += " AND (u.status=? OR u.status=?) "
+		pars = append(pars, 0, 2)
+	}
+	switch IsRegistered {
+	case "是":
+		condition += " AND u.is_registered=? "
+		pars = append(pars, true)
+	case "否":
+		condition += " AND u.is_registered=? "
+		pars = append(pars, false)
+	}
+	switch IsSubscribed {
+	case "是":
+		condition += " AND u.is_subscribed=? "
+		pars = append(pars, true)
+	case "否":
+		condition += " AND u.is_subscribed=? "
+		pars = append(pars, false)
+	}
+	if registerStartDate != "" {
+		registerStartTime, er := time.Parse(utils.FormatDate, registerStartDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		condition += " AND u.register_time>=? "
+		registerStartDateStr := registerStartTime.Format(utils.FormatDateTime)
+		pars = append(pars, registerStartDateStr)
+	}
+	if registerEndDate != "" {
+		registerEndTime, er := time.Parse(utils.FormatDate, registerEndDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		condition += " AND u.register_time<=? "
+		// 结束时间包含今天
+		registerEndTime = registerEndTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
+		registerEndDateStr := registerEndTime.Format(utils.FormatDateTime)
+		pars = append(pars, registerEndDateStr)
+	}
+	if createStartDate != "" {
+		createStartTime, er := time.Parse(utils.FormatDate, createStartDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		condition += " AND u.create_time>=? "
+		createStartDateStr := createStartTime.Format(utils.FormatDateTime)
+		pars = append(pars, createStartDateStr)
+	}
+	if createEndDate != "" {
+		createEndTime, er := time.Parse(utils.FormatDate, createEndDate)
+		if er != nil {
+			br.Msg = "日期格式有误"
+			return
+		}
+		condition += " AND u.create_time<=? "
+		// 结束时间包含今天
+		createEndTime = createEndTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
+		createEndDateStr := createEndTime.Format(utils.FormatDateTime)
+		pars = append(pars, createEndDateStr)
+	}
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	} else if pageSize > utils.PageSize100 {
+		pageSize = utils.PageSize100
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize := utils.StartIndex(currentIndex, pageSize)
+
+	total, err := models.GetUserReadCount(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	userList, err := models.GetUserReadList(condition, sortCondition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "查询用户失败"
+		br.Msg = "查询用户失败,系统错误,Err:" + err.Error()
+		return
+	}
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(response.UserListResp)
+	resp.Paging = page
+	resp.List = userList
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Detail
+// @Title 用户阅读记录详情
+// @Description 用户阅读记录详情信息
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ChartPermissionIds   query   string  true       "品种列表"
+// @Param   ClassifyIds   query   string  true       "品种列表"
+// @Success 200 {object} models.LoginResp
+// @router /detail [get]
+func (this *UserReadRecordController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	UserId, _ := this.GetInt("UserId")
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	chartPermissionids := this.GetString("ChartPermissionIds")
+	classifyIds := this.GetString("ClassifyIds")
+	if UserId <= 0 {
+		br.Msg = "查询用户不存在"
+		return
+	}
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	} else if pageSize > utils.PageSize100 {
+		pageSize = utils.PageSize100
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize := utils.StartIndex(currentIndex, pageSize)
+	user, err := models.GetUserById(UserId)
+	if err != nil {
+		if err == orm.ErrNoRows {
+			br.Msg = "用户不存在或已删除,请刷新页面"
+			return
+		}
+		br.Msg = "查询用户失败"
+		br.ErrMsg = "查询用户失败,系统错误,Err:" + err.Error()
+		return
+	}
+	if user == nil {
+		br.Msg = "用户不存在或已删除,请刷新页面"
+		return
+	}
+	var condition string
+	var pars []interface{}
+
+	if chartPermissionids != "" {
+		ids := strings.Split(chartPermissionids, ",")
+		if len(ids) != 0 {
+			condition += ` AND ( `
+			for i, id := range ids {
+				if i == 0 {
+					condition += ` urp2.chart_permission_id = ? `
+					pars = append(pars, id)
+				} else {
+					condition += ` OR urp2.chart_permission_id = ? `
+					pars = append(pars, id)
+				}
+			}
+			condition += `) `
+		}
+	}
+	if classifyIds != "" {
+		ids := strings.Split(classifyIds, ",")
+		if len(ids) != 0 {
+			condition += ` AND ( `
+			for i, id := range ids {
+				if i == 0 {
+					condition += ` classify_id2 = ? `
+					pars = append(pars, id)
+				} else {
+					condition += ` OR classify_id2 = ? `
+					pars = append(pars, id)
+				}
+			}
+			condition += `) `
+		}
+	}
+
+	total, err := models.GetUserReadRecordCountByUserId(UserId, condition, pars)
+	if err != nil {
+		br.Msg = "查询阅读记录失败"
+		br.ErrMsg = "查询阅读记录失败,Err:" + err.Error()
+		return
+	}
+	readList, err := models.GetUserReadRecordByUserId(UserId, condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "查询阅读记录失败"
+		br.ErrMsg = "查询阅读记录失败,系统错误,Err:" + err.Error()
+		return
+	}
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(response.UserReadRecordListResp)
+	resp.Paging = page
+	resp.List = readList
+
+	br.Msg = "获取成功"
+	br.Data = resp
+	br.Success = true
+	br.Ret = 200
+}
+
+// ReadCntChart
+// @Title 用户阅读量统计图信息
+// @Description 用户阅读量统计图信息
+// @Param   ChartPermissionIds   query   string  true       "品种列表"
+// @Param   ClassifyIds   query   string  true       "品种列表"
+// @Param   StartDate   query   string  true       "开始时间"
+// @Param   EndDate   query   string  true       "结束时间"
+// @Param   UserId   query   int  true       "用户id"
+// @Success 200 {object} models.LoginResp
+// @router /readCntChart [get]
+func (this *UserReadRecordController) ReadCntChart() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	userId, _ := this.GetInt("UserId")
+	startDate := this.GetString("StartDate")
+	endDate := this.GetString("EndDate")
+	chartPermissionIds := this.GetString("ChartPermissionIds")
+	classifyIds := this.GetString("ClassifyIds")
+	var condition string
+	var pars []interface{}
+
+	if chartPermissionIds != "" {
+		ids := strings.Split(chartPermissionIds, ",")
+		if len(ids) != 0 {
+			condition += ` AND ( `
+			for i, id := range ids {
+				if i == 0 {
+					condition += ` urp.chart_permission_id = ? `
+					pars = append(pars, id)
+				} else {
+					condition += ` OR urp.chart_permission_id = ? `
+					pars = append(pars, id)
+				}
+			}
+			condition += `) `
+		}
+	}
+	if classifyIds != "" {
+		ids := strings.Split(classifyIds, ",")
+		if len(ids) != 0 {
+			condition += ` AND ( `
+			for i, id := range ids {
+				if i == 0 {
+					condition += ` classify_id2 = ? `
+					pars = append(pars, id)
+				} else {
+					condition += ` OR classify_id2 = ? `
+					pars = append(pars, id)
+				}
+			}
+			condition += `) `
+		}
+	}
+
+	if startDate == "" {
+		startDate = time.Now().AddDate(-1, 0, 0).Format(utils.FormatDate)
+	}
+	if endDate == "" {
+		endDate = time.Now().AddDate(0, 0, 1).Format(utils.FormatDate)
+	}
+	if userId > 0 {
+		condition += ` AND ur.user_id = ? `
+		pars = append(pars, userId)
+	}
+	readCnts, err := models.GetStaticReadCnt(condition, pars, startDate, endDate)
+	if err != nil {
+		br.Msg = "获取阅读统计失败"
+		br.ErrMsg = "获取阅读统计失败,系统错误,Err:" + err.Error()
+		return
+	}
+
+	br.Msg = "查询成功"
+	br.Data = readCnts
+	br.Ret = 200
+	br.Success = true
+}
+
+// readPermissionChart
+// @Title 用户阅读品种图信息
+// @Description 用户阅读品种图信息
+// @Param   UserId   query   string  true       "用户id"
+// @Success 200 {object} models.LoginResp
+// @router /readPermissionChart [get]
+func (this *UserReadRecordController) ReadPermissionChart() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	userId, _ := this.GetInt("UserId")
+
+	var condition string
+	var pars []interface{}
+	if userId > 0 {
+		condition += ` AND ur.user_id = ? `
+		pars = append(pars, userId)
+	}
+
+	permissionCnts, err := models.GetStaticPermissionCnt(condition, pars)
+	if err != nil {
+		br.Msg = "获取品种阅读统计失败"
+		br.ErrMsg = "获取品种阅读统计失败,系统错误,Err:" + err.Error()
+		return
+	}
+	var sum int
+	for _, v := range permissionCnts {
+		sum += v.Count
+	}
+	for _, v := range permissionCnts {
+		percent := float64(v.Count) / float64(sum) * 100
+		percentStr := fmt.Sprintf("%.0f", percent)
+		parsedFloat, _ := strconv.ParseFloat(percentStr, 64)
+		v.Percent = parsedFloat
+	}
+
+	br.Msg = "查询成功"
+	br.Data = permissionCnts
+	br.Ret = 200
+	br.Success = true
+}

+ 32 - 2
go.mod

@@ -2,5 +2,35 @@ module eta/eta_mini_crm
 
 go 1.21
 
-require github.com/beego/beego/v2 v2.1.0
-require github.com/smartystreets/goconvey v1.6.4
+require (
+	github.com/beego/bee/v2 v2.1.0
+	github.com/beego/beego/v2 v2.1.0
+	github.com/dgrijalva/jwt-go v3.2.0+incompatible
+	github.com/go-redis/redis/v8 v8.11.5
+	github.com/go-sql-driver/mysql v1.7.0
+	github.com/rdlucklib/rdluck_tools v1.0.3
+	github.com/robfig/cron/v3 v3.0.1
+)
+
+require (
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/cespare/xxhash/v2 v2.2.0 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/hashicorp/golang-lru v0.5.4 // indirect
+	github.com/kr/text v0.2.0 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/prometheus/client_golang v1.15.1 // indirect
+	github.com/prometheus/client_model v0.3.0 // indirect
+	github.com/prometheus/common v0.42.0 // indirect
+	github.com/prometheus/procfs v0.9.0 // indirect
+	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
+	golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
+	golang.org/x/net v0.7.0 // indirect
+	golang.org/x/sys v0.6.0 // indirect
+	golang.org/x/text v0.7.0 // indirect
+	google.golang.org/protobuf v1.30.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 54 - 2
main.go

@@ -1,11 +1,63 @@
 package main
 
 import (
+	"eta/eta_mini_crm/controllers"
 	_ "eta/eta_mini_crm/routers"
-	beego "github.com/beego/beego/v2/server/web"
+	"eta/eta_mini_crm/scheduler"
+	"eta/eta_mini_crm/utils"
+	"fmt"
+	"runtime"
+	"time"
+
+	"github.com/beego/beego/v2/core/logs"
+	"github.com/beego/beego/v2/server/web"
+	"github.com/beego/beego/v2/server/web/context"
 )
 
 func main() {
-	beego.Run()
+	if web.BConfig.RunMode == "dev" {
+		web.BConfig.WebConfig.DirectoryIndex = true
+		web.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
+	}
+
+	// 启动定时任务
+	go scheduler.InitJob()
+
+	web.ErrorController(&controllers.ErrorController{})
+	// 内存调整
+	web.BConfig.MaxMemory = 1024 * 1024 * 128
+
+	web.BConfig.RecoverFunc = Recover
+	web.Run()
 }
 
+func Recover(ctx *context.Context, conf *web.Config) {
+	if err := recover(); err != nil {
+		if err == web.ErrAbort {
+			return
+		}
+		if !web.BConfig.RecoverPanic {
+			panic(err)
+		}
+		stack := ""
+		msg := fmt.Sprintf("The request url is  %v", ctx.Input.URL())
+		stack += msg + "</br>"
+		logs.Critical(msg)
+		msg = fmt.Sprintf("The request data is %v", string(ctx.Input.RequestBody))
+		stack += msg + "</br>"
+		logs.Critical(msg)
+		msg = fmt.Sprintf("Handler crashed with error %v", err)
+		stack += msg + "</br>"
+		logs.Critical(msg)
+		for i := 1; ; i++ {
+			_, file, line, ok := runtime.Caller(i)
+			if !ok {
+				break
+			}
+			logs.Critical(fmt.Sprintf("%s:%d", file, line))
+			stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d</br>", file, line))
+		}
+		utils.ApiLog.Notice(utils.APPNAME + "崩了" + time.Now().Format("2006-01-02 15:04:05") + "<br/>" + stack)
+	}
+	return
+}

+ 16 - 0
models/base.go

@@ -0,0 +1,16 @@
+package models
+
+type BaseResponse struct {
+	Ret         int
+	Msg         string
+	ErrMsg      string
+	ErrCode     string
+	Data        interface{}
+	Success     bool `description:"true 执行成功,false 执行失败"`
+	IsSendEmail bool `json:"-" description:"true 发送邮件,false 不发送邮件"`
+	IsAddLog    bool `json:"-" description:"true 新增操作日志,false 不新增操作日志" `
+}
+
+func (r *BaseResponse) Init() *BaseResponse {
+	return &BaseResponse{Ret: 403, IsSendEmail: true}
+}

+ 121 - 0
models/chart_permission.go

@@ -0,0 +1,121 @@
+package models
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type ChartPermission struct {
+	ChartPermissionId     int       `orm:"column(chart_permission_id);pk" description:"问题ID" json:"chart_permission_id"`
+	ChartPermissionName   string    `description:"名称" json:"chart_permission_name"`
+	PermissionName        string    `description:"权限名" json:"permission_name"`
+	Sort                  int       `description:"排序" json:"sort"`
+	Enabled               int       `description:"是否可用" json:"enabled"`
+	CreatedTime           time.Time `description:"创建时间" json:"created_time"`
+	LastUpdatedTime       time.Time `description:"更新时间" json:"last_updated_time"`
+	TeleconferenceSort    int       `description:"电话会类型排序" json:"teleconference_sort"`
+	Remark                string    `description:"备注" json:"remark"`
+	ClassifyName          string    `description:"分类名称" json:"classify_name"`
+	ProductName           string    `description:"产品名称" json:"product_name"`
+	ProductId             int       `description:"产品ID" json:"product_id"`
+	ImageURL              string    `orm:"column(image_url);" description:"图片地址" json:"image_url"`
+	ShowType              int       `description:"1:查研观向小程序展示" json:"show_type"`
+	IsOther               int       `description:"是否是其他,用于查研观向小程序后台展示" json:"is_other"`
+	IsReport              int       `description:"是否是报告,用于查研观向小程序前台报告展示" json:"is_report"`
+	CygxAuth              int       `description:"是否是权限,用于查研观向小程序前台权限校验" json:"cygx_auth"`
+	PermissionType        int       `description:"1主观,2客观" json:"permission_type"`
+	YbImgUrl              string    `description:"研报小程序报告列表icon" json:"yb_img_url"`
+	ProductPermissionName string    `description:"种类权限名称" json:"product_permission_name"`
+	PriceDrivenState      int       `description:"品种价格驱动开启状态 0-关闭 1-开启" json:"price_driven_state"`
+	ImageUrlM             string    `description:"图片地址(查研观向移动端)" json:"image_url_m"`
+	ParentId              int       `description:"父级权限id" json:"parent_id"`
+	IsPublic              int       `description:"是否是公有权限1:公有权限,0私有权限" json:"is_public"`
+}
+
+type ChartPermissionList struct {
+	ChartPermissionId     int                    `orm:"column(chart_permission_id);pk" description:"问题ID" json:"chart_permission_id"`
+	ChartPermissionName   string                 `description:"名称" json:"chart_permission_name"`
+	PermissionName        string                 `description:"权限名" json:"permission_name"`
+	Sort                  int                    `description:"排序" json:"sort"`
+	Enabled               int                    `description:"是否可用" json:"enabled"`
+	CreatedTime           time.Time              `description:"创建时间" json:"created_time"`
+	LastUpdatedTime       time.Time              `description:"更新时间" json:"last_updated_time"`
+	TeleconferenceSort    int                    `description:"电话会类型排序" json:"teleconference_sort"`
+	Remark                string                 `description:"备注" json:"remark"`
+	ClassifyName          string                 `description:"分类名称" json:"classify_name"`
+	ProductName           string                 `description:"产品名称" json:"product_name"`
+	ProductId             int                    `description:"产品ID" json:"product_id"`
+	ImageURL              string                 `orm:"column(image_url);" description:"图片地址" json:"image_url"`
+	ShowType              int                    `description:"1:查研观向小程序展示" json:"show_type"`
+	IsOther               int                    `description:"是否是其他,用于查研观向小程序后台展示" json:"is_other"`
+	IsReport              int                    `description:"是否是报告,用于查研观向小程序前台报告展示" json:"is_report"`
+	CygxAuth              int                    `description:"是否是权限,用于查研观向小程序前台权限校验" json:"cygx_auth"`
+	PermissionType        int                    `description:"1主观,2客观" json:"permission_type"`
+	YbImgUrl              string                 `description:"研报小程序报告列表icon" json:"yb_img_url"`
+	ProductPermissionName string                 `description:"种类权限名称" json:"product_permission_name"`
+	PriceDrivenState      int                    `description:"品种价格驱动开启状态 0-关闭 1-开启" json:"price_driven_state"`
+	ImageUrlM             string                 `description:"图片地址(查研观向移动端)" json:"image_url_m"`
+	ParentId              int                    `description:"父级权限id" json:"parent_id"`
+	IsPublic              int                    `description:"是否是公有权限1:公有权限,0私有权限" json:"is_public"`
+	Child                 []*ChartPermissionList `description:"子权限"`
+}
+
+type ChartPermissionView struct {
+	ParentName     string `description:"父权限名称" json:"parent_name"`
+	PermissionName string `description:"权限名" json:"permission_name"`
+}
+
+type ChartPermissionListTree struct {
+	ChartPermissionId int                        `description:"权限ID"`
+	PermissionName    string                     `description:"权限名"`
+	ParentId          int                        `description:"父权限ID"`
+	IsPublic          int                        `description:"是否是公有权限"`
+	PublicChild       []*ChartPermissionListTree `description:"公有权限"`
+	PrivateChild      []*ChartPermissionListTree `description:"私有权限"`
+}
+
+func GetChartPermissionListByCondition(condition string, pars []interface{}) (items []*ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission WHERE enabled=1 `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars...).QueryRows(items)
+	return
+}
+
+func GetChartPermissionListByIds(chartPermissionIds []int) (items []*ChartPermissionView, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT c.permission_name AS permission_name, lc.permission_name AS parent_name FROM chart_permission c
+		LEFT JOIN chart_permission lc
+		ON c.parent_id=lc.chart_permission_id 
+		WHERE c.chart_permission_id in (%s)
+	`
+	// 构建 IN 子句的占位符
+	placeholders := make([]string, len(chartPermissionIds))
+	args := make([]interface{}, len(chartPermissionIds))
+	for i, id := range chartPermissionIds {
+		placeholders[i] = "?"
+		args[i] = id
+	}
+	sql = fmt.Sprintf(sql, strings.Join(placeholders, ","))
+	_, err = o.Raw(sql, chartPermissionIds).QueryRows(&items)
+	return
+}
+
+func GetChartPermissionList() (items []*ChartPermissionListTree, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission WHERE enabled=1 AND product_id=1 ORDER BY sort, chart_permission_id ASC`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetChartPermissionByParentId(parentId int) (items []*ChartPermissionList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission WHERE enabled=1 AND chart_permission_id=?`
+	_, err = o.Raw(sql, parentId).QueryRows(&items)
+	return
+}

+ 37 - 0
models/classify.go

@@ -0,0 +1,37 @@
+package models
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type Classify struct {
+	Id            int       `orm:"column(id);pk"`
+	ClassifyName  string    `description:"分类名称"`
+	Sort          int       `json:"-"`
+	ParentId      int       `description:"父级分类id"`
+	CreateTime    time.Time `description:"创建时间"`
+	ModifyTime    time.Time `description:"修改时间"`
+	ClassifyLabel string    `description:"分类标签"`
+	Enabled       int       `description:"是否可用,1可用,0禁用"`
+}
+
+type ClassifyView struct {
+	Id            int             `orm:"column(id);pk"`
+	ClassifyName  string          `description:"分类名称"`
+	Sort          int             `json:"-"`
+	ParentId      int             `description:"父级分类id"`
+	CreateTime    time.Time       `description:"创建时间"`
+	ModifyTime    time.Time       `description:"修改时间"`
+	ClassifyLabel string          `description:"分类标签"`
+	Enabled       int             `description:"是否可用,1可用,0禁用"`
+	Child         []*ClassifyView `description:"子分类"`
+}
+
+func GetClassifyList() (items []*ClassifyView, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM classify WHERE enabled=1`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}

+ 16 - 0
models/crm_config.go

@@ -0,0 +1,16 @@
+package models
+
+import "github.com/beego/beego/v2/client/orm"
+
+const ConfAreaCodeListKey = "area_code_list" // 手机号区号列表
+
+type CrmConfig struct {
+	ConfigValue string `description:"详情"`
+}
+
+func GetConfigDetailByCode(configCode string) (item CrmConfig, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT config_value FROM crm_config WHERE config_code=? `
+	err = o.Raw(sql, configCode).QueryRow(&item)
+	return
+}

+ 43 - 0
models/db.go

@@ -0,0 +1,43 @@
+package models
+
+import (
+	"eta/eta_mini_crm/utils"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+	_ "github.com/go-sql-driver/mysql"
+)
+
+func init() {
+	_ = orm.RegisterDataBase("rddp", "mysql", utils.MYSQL_URL_RDDP)
+	orm.SetMaxIdleConns("rddp", 50)
+	orm.SetMaxOpenConns("rddp", 100)
+
+	report_db, _ := orm.GetDB("rddp")
+	report_db.SetConnMaxLifetime(10 * time.Minute)
+
+	_ = orm.RegisterDataBase("default", "mysql", utils.MYSQL_URL_MASTER)
+	orm.SetMaxIdleConns("default", 50)
+	orm.SetMaxOpenConns("default", 100)
+
+	db, _ := orm.GetDB("default")
+	db.SetConnMaxLifetime(10 * time.Minute)
+
+	orm.Debug = true
+	orm.DebugLog = orm.NewLog(utils.BinLog)
+
+	orm.RegisterModel(
+		new(SysSession),
+		new(SysRole),
+		new(SysUser),
+		new(SysDepartment),
+		new(SysMenu),
+		new(SysRoleMenuMapping),
+		new(ChartPermission),
+		new(UserChartPermissionMapping),
+		new(User),
+		new(SysMessageReport),
+		new(CrmConfig),
+		new(UserChangeRecord),
+	)
+}

+ 5 - 0
models/request/chart_permission.go

@@ -0,0 +1,5 @@
+package request
+
+type ChartPermissionListReq struct {
+	UserId int `description:"用户ID"`
+}

+ 23 - 0
models/request/sys_department.go

@@ -0,0 +1,23 @@
+package request
+
+type SysDepartmentAddReq struct {
+	SysDepartmentId    int      `description:"部门id"`
+	SysDepartmentNames []string `description:"部门名称"`
+	Level              int      `description:"目录等级"`
+}
+
+type SysDepartmentEditReq struct {
+	SysDepartmentId   int    `description:"部门ID"`
+	SysDepartmentName string `description:"部门名称"`
+}
+
+type SysDepartmentDeleteReq struct {
+	SysDepartmentId int `description:"部门ID"`
+	Level           int `description:"目录层级"`
+}
+
+type SysDepartmentSortReq struct {
+	DepartmentIds1 []int `description:"移动后的一级ID排序"`
+	DepartmentIds2 []int `description:"移动后的二级ID排序"`
+	DepartmentIds3 []int `description:"移动后的三级ID排序"`
+}

+ 5 - 0
models/request/sys_message_report.go

@@ -0,0 +1,5 @@
+package request
+
+type SysMessageReadReq struct {
+	SysMessageId []int
+}

+ 20 - 0
models/request/sys_role.go

@@ -0,0 +1,20 @@
+package request
+
+type SysRoleDeleteReq struct {
+	SysRoleId int `description:"角色ID"`
+}
+
+type SysRoleAddReq struct {
+	SysRoleName string `description:"角色名称"`
+}
+
+type SysRoleEditReq struct {
+	SysRoleId   int    `description:"角色ID"`
+	SysRoleName string `description:"角色名称"`
+}
+
+type RoleMenusSaveReq struct {
+	SysRoleId   int   `description:"角色ID"`
+	SysMenuIds  []int `description:"菜单IDs"`
+	HalfMenuIds []int `description:"半选菜单IDs-仅供前端回显用的"`
+}

+ 44 - 0
models/request/sys_user.go

@@ -0,0 +1,44 @@
+package request
+
+type SysUserInfoReq struct {
+	SysUserId       int    `description:"id"`
+	SysUserName     string `description:"账号"`
+	Password        string `description:"密码"`
+	RealName        string `description:"姓名"`
+	Phone           string `description:"手机号"`
+	AreaCode        string `description:"区号"`
+	SysDepartmentId int    `description:"部门id"`
+	Email           string `description:"邮箱"`
+	SysRoleId       int    `description:"角色id"`
+	Province        string `description:"省"`
+	City            string `description:"市"`
+	IsEnabled       bool   `description:"是否启用"`
+}
+
+type ResetPasswordReq struct {
+	SysUserId  int    `description:"用户id"`
+	Password   string `description:"密码"`
+	RePassword string `description:"重复密码"`
+}
+
+type ResetMyPasswordReq struct {
+	OriginPassword string `description:"旧密码"`
+	Password       string `description:"新密码"`
+	RePassword     string `description:"重复密码"`
+}
+
+type SysUserEditEnabledReq struct {
+	SysUserId int  `description:"用户id"`
+	IsEnabled bool `description:"是否启用"`
+}
+
+type MoveToDepartmentReq struct {
+	SysUserId       int `description:"用户id"`
+	SysDepartmentId int `description:"部门id"`
+}
+
+type UserLoginReq struct {
+	UserName string `description:"账号"`
+	Password string `description:"密码"`
+	ReqTime  string `description:"登录时间戳"`
+}

+ 44 - 0
models/request/user.go

@@ -0,0 +1,44 @@
+package request
+
+type UserAddReq struct {
+	RealName        string `description:"姓名"`
+	AreaCode        string `description:"区号"`
+	Phone           string `description:"手机号"`
+	Email           string `description:"邮箱"`
+	SellerId        int    `description:"销售id"`
+	ValidStartTime  string `description:"有效期开始时间"`
+	ValidEndTime    string `description:"有效期结束时间"`
+	Company         string `description:"所属公司"`
+	ChartPermission []int  `description:"所选品种"`
+}
+
+type UserEidtReq struct {
+	UserId          int    `description:"用户id"`
+	RealName        string `description:"姓名"`
+	AreaCode        string `description:"区号"`
+	Phone           string `description:"手机号"`
+	Email           string `description:"邮箱"`
+	SellerId        int    `description:"销售id"`
+	ValidStartTime  string `description:"有效期开始时间"`
+	ValidEndTime    string `description:"有效期结束时间"`
+	Company         string `description:"所属公司"`
+	ChartPermission []int  `description:"所选品种"`
+	IsEnabled       bool   `description:"是否禁用"`
+}
+
+type UserCheckReq struct {
+	AreaCode string `description:"区号"`
+	Phone    string `description:"手机号"`
+	Email    string `description:"邮箱"`
+}
+
+type UserDeleteReq struct {
+	UserId int `description:"用户id"`
+}
+
+type UserEditEnabledReq struct {
+	UserId         int    `description:"用户id"`
+	IsEnabled      bool   `description:"是否启用"`
+	ValidStartTime string `description:"有效期开始时间"`
+	ValidEndTime   string `description:"有效期结束时间"`
+}

+ 8 - 0
models/response/chart_permission.go

@@ -0,0 +1,8 @@
+package response
+
+import "eta/eta_mini_crm/models"
+
+type ChartPermissionListresp struct {
+	List         []*models.ChartPermissionListTree
+	SelectedList []int
+}

+ 13 - 0
models/response/seller.go

@@ -0,0 +1,13 @@
+package response
+
+type DepartmentSellers struct {
+	SysUserId         int                  `description:"系统用户id"`
+	SysRealName       string               `description:"用户真实名称"`
+	SysDepartmentId   int                  `description:"系统部门id"`
+	SysDepartmentName string               `description:"部门名称"`
+	ChildrenList      []*DepartmentSellers `description:"销售列表"`
+}
+
+type DepartmentGroupSellersResp struct {
+	List []*DepartmentSellers
+}

+ 7 - 0
models/response/sys_department.go

@@ -0,0 +1,7 @@
+package response
+
+import "eta/eta_mini_crm/models"
+
+type SysDepartmentListResp struct {
+	List []*models.SysDepartmentList
+}

+ 20 - 0
models/response/sys_menu.go

@@ -0,0 +1,20 @@
+package response
+
+import (
+	"eta/eta_mini_crm/models"
+)
+
+type SysMenuButtonResp struct {
+	SysMenuId  int    `description:"菜单ID"`
+	ParentId   int    `description:"父级菜单ID"`
+	Name       string `description:"菜单名称或者按钮名称"`
+	MenuType   int    `description:"菜单类型: 0-菜单; 1-按钮"`
+	ButtonCode string `description:"按钮唯一标识"`
+}
+
+// SysMenuListResp ETA商家菜单列表响应体
+type SysMenuListResp struct {
+	ChoiceList     []int                 `description:"已选菜单"`
+	HalfChoiceList []int                 `description:"半选菜单-方便前端回显用的"`
+	List           []*models.SysMenuItem `description:"菜单列表"`
+}

+ 12 - 0
models/response/sys_message_report.go

@@ -0,0 +1,12 @@
+package response
+
+import (
+	"eta/eta_mini_crm/models"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type SysMessageListResp struct {
+	List   []*models.SysMessageReportView
+	Paging *paging.PagingItem `description:"分页数据"`
+}

+ 12 - 0
models/response/sys_role.go

@@ -0,0 +1,12 @@
+package response
+
+import (
+	"eta/eta_mini_crm/models"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type SysRoleListResp struct {
+	List   []*models.SysRole
+	Paging *paging.PagingItem `description:"分页数据"`
+}

+ 21 - 0
models/response/sys_user.go

@@ -0,0 +1,21 @@
+package response
+
+import (
+	"eta/eta_mini_crm/models"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type SysUserListResp struct {
+	List   []models.SysUserView
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type LoginResp struct {
+	Authorization string
+	SysUserName   string `description:"系统用户名"`
+	SysRealName   string `description:"系统用户姓名"`
+	SysUserId     int    `description:"系统用户id"`
+	RoleName      string `description:"所属角色名称"`
+	RoleId        int    `description:"所属角色id"`
+}

+ 27 - 0
models/response/user.go

@@ -0,0 +1,27 @@
+package response
+
+import (
+	"eta/eta_mini_crm/models"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type UserListResp struct {
+	List   []*models.UserView
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type UserCheckResp struct {
+	UserId int
+	Status int
+}
+
+type UserChangeRecordResp struct {
+	List []*models.UserChangeRecord
+	// Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type UserDetailResp struct {
+	Detail     *models.UserView
+	Permission map[string][]string
+}

+ 6 - 0
models/response/user_login.go

@@ -0,0 +1,6 @@
+package response
+
+type AreaCodeListResp struct {
+	Name  string `description:"地区"`
+	Value string `description:"区号"`
+}

+ 17 - 0
models/response/user_read_record.go

@@ -0,0 +1,17 @@
+package response
+
+import (
+	"eta/eta_mini_crm/models"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type UserReadRecordListResp struct {
+	Paging *paging.PagingItem
+	List   []*models.UserReadRecord
+}
+
+type StaticInfoResp struct {
+	ReadCntList       []*models.ReadCntStaitc
+	PermissionCntList []*models.PermissionCntStaitc
+}

+ 184 - 0
models/sys_department.go

@@ -0,0 +1,184 @@
+package models
+
+import (
+	"context"
+	"eta/eta_mini_crm/utils"
+	"fmt"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type SysDepartment struct {
+	SysDepartmentId   int    `orm:"pk" description:"部门id"`
+	SysDepartmentName string `description:"部门名称"`
+	Sort              int    `description:"排序"`
+	Level             int    `description:"层级"`
+	ParentId          int    `description:"父目录id"`
+}
+
+func (s *SysDepartment) Add() (err error) {
+	o := orm.NewOrm()
+	_, err = o.Insert(s)
+	return
+}
+
+func (s *SysDepartment) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(s, cols...)
+	return
+}
+
+type SysDepartmentList struct {
+	SysDepartmentId   int                  `description:"部门id"`
+	SysDepartmentName string               `description:"部门名称"`
+	Child             []*SysDepartmentList `description:"分组"`
+	Level             int                  `description:"1:部门, 2|3:分组"`
+	ParentId          int                  `description:"父目录id"`
+}
+
+func DeleteSysDepartmentById(sysDepartmentIds []string, level int) (err error) {
+	o := orm.NewOrm()
+	err = o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
+		sql := `DELETE FROM sys_department WHERE sys_department_id IN (?) `
+		_, e := txOrm.QueryTable(&SysDepartment{}).
+			Filter("sys_department_id__in", sysDepartmentIds).
+			Delete()
+		if e != nil {
+			return e
+		}
+		if level == 1 {
+			sql = `DELETE FROM sys_user_department_mapping WHERE sys_department_id1=?`
+			_, e = txOrm.Raw(sql, sysDepartmentIds[0]).Exec()
+			if e != nil {
+				return e
+			}
+		} else {
+			sql = `UPDATE sys_user_department_mapping SET `
+			for i := level; i <= utils.MaxDepartmentLevel; i++ {
+				sql += fmt.Sprintf("sys_department_id%d=0 ", i)
+				if i != utils.MaxDepartmentLevel {
+					sql += ","
+				}
+			}
+			sql += fmt.Sprintf("WHERE sys_department_id%d=?", level)
+			_, e = txOrm.Raw(sql, sysDepartmentIds[0]).Exec()
+			if e != nil {
+				return e
+			}
+		}
+		return nil
+	})
+	return
+}
+
+func DeleteSysDepartmentByIdV2(sysDepartmentIds []string, level int) (err error) {
+	o := orm.NewOrm()
+	err = o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
+		// sql := `DELETE FROM sys_department WHERE sys_department_id IN (?) `
+		_, e := txOrm.QueryTable(&SysDepartment{}).
+			Filter("sys_department_id__in", sysDepartmentIds).
+			Delete()
+		if e != nil {
+			return e
+		}
+		sql := `UPDATE sys_user SET `
+		for i := level; i <= utils.MaxDepartmentLevel; i++ {
+			sql += fmt.Sprintf("sys_department_id%d=0 ", i)
+			if i != utils.MaxDepartmentLevel {
+				sql += ","
+			}
+		}
+		sql += fmt.Sprintf("WHERE sys_department_id%d=?", level)
+		_, e = txOrm.Raw(sql, sysDepartmentIds[0]).Exec()
+		if e != nil {
+			return e
+		}
+		return nil
+	})
+	return
+}
+
+func UpdateDepartmentSortByIds(sysDepartmentIds []int) (err error) {
+	o := orm.NewOrm()
+	err = o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
+		sql := `UPDATE sys_department SET sort=? WHERE sys_department_id=?`
+		for i, depId := range sysDepartmentIds {
+			_, e := txOrm.Raw(sql, i+1, depId).Exec()
+			if e != nil {
+				return e
+			}
+		}
+		return nil
+	})
+	return
+}
+
+func GetSysDepartmentById(sysDepartmentId int) (item *SysDepartment, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_department WHERE sys_department_id=? `
+	err = o.Raw(sql, sysDepartmentId).QueryRow(&item)
+	return
+}
+
+func GetSysDepartments() (items []*SysDepartment, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_department `
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetSysDepartmentList() (items []*SysDepartmentList, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_department ORDER BY sort`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetSysDepartmentCountById(sysDepartmentId int) (count int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT COUNT(1) AS count FROM sys_department WHERE sys_department_id=?`
+	err = o.Raw(sql, sysDepartmentId).QueryRow(&count)
+	return
+}
+
+func GetSysDepartmentCountByParentId(parentId int, sysDepartmentName string) (count int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT COUNT(1) AS count FROM sys_department WHERE parent_id=? AND sys_department_name=?`
+	err = o.Raw(sql, parentId, sysDepartmentName).QueryRow(&count)
+	return
+}
+
+func GetSysDepartmentCountByName(sysDepartmentName string) (count int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT COUNT(1) AS count FROM sys_department WHERE sys_department_name=?`
+	err = o.Raw(sql, sysDepartmentName).QueryRow(&count)
+	return
+}
+
+func GetSysDepartmentByName(sysDepartmentName string) (item *SysDepartment, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_department WHERE sys_department_name=?`
+	err = o.Raw(sql, sysDepartmentName).QueryRow(&item)
+	return
+}
+
+func GetSysDepartmentListByParentId(parentId int) (item SysDepartmentList, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_department WHERE sys_department_id=? `
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+func GetChildSysDepartmentListById(sysDepartmentId int) (items []*SysDepartmentList, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_department WHERE parent_id=? `
+	_, err = o.Raw(sql, sysDepartmentId).QueryRows(&items)
+	return
+}
+
+func GetChildSysDepartmentListByIds(sysDepartmentIds string) (items []*SysDepartmentList, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_department WHERE parent_id in (?) `
+	_, err = o.Raw(sql, sysDepartmentIds).QueryRows(&items)
+	return
+}

+ 89 - 0
models/sys_menu.go

@@ -0,0 +1,89 @@
+package models
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type SysMenu struct {
+	SysMenuId  int       `orm:"pk" description:"菜单id"`
+	ParentId   int       `description:"父级id"`
+	Sort       int       `description:"排序id"`
+	ButtonCode string    `description:"按钮唯一编码"`
+	Name       string    `description:"按钮名称"`
+	Path       string    `description:"路径"`
+	Api        string    `description:"api接口"`
+	IconPath   string    `description:"图标路径"`
+	Level      int       `description:"层级"`
+	MenuType   int       `description:"菜单类型,1:菜单,2:按钮"`
+	CreateTime time.Time `description:"创建时间"`
+	ModifyTime time.Time `description:"修改时间"`
+}
+
+type SysRoleMenuMapping struct {
+	SysRoleMenuMappingId int `orm:"pk" description:"id"`
+	SysRoleId            int `description:"角色id"`
+	SysMenuId            int `description:"菜单id"`
+	Type                 int `description:"类型"`
+}
+
+// SysMenuItem 角色菜单
+type SysMenuItem struct {
+	SysMenuId  int
+	ParentId   int            `description:"父级菜单ID"`
+	Name       string         `description:"菜单名称或者按钮名称"`
+	Level      int            `description:"层级"`
+	Sort       int            `description:"排序"`
+	Path       string         `description:"路由地址"`
+	IconPath   string         `description:"菜单图标地址"`
+	ButtonCode string         `description:"按钮/菜单唯一标识"`
+	Children   []*SysMenuItem `description:"子菜单"`
+}
+
+// GetMenuButtonsByRoleId 获取角色按钮
+func GetMenuButtonsByRoleId(roleId int) (items []*SysMenu, err error) {
+	sql := `SELECT
+				r.*
+			FROM
+				sys_menu AS r
+			JOIN sys_role_menu_mapping AS rm ON r.sys_menu_id = rm.sys_menu_id 
+			WHERE
+				rm.sys_role_id = ? 
+			ORDER BY
+				r.sort ASC,
+				r.create_time DESC`
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, roleId).QueryRows(&items)
+	return
+}
+
+// GetMenuListByRoleIds 根据管理员角色id查询菜单
+func GetMenuListByRoleIds(roleId int) (items []*SysMenuItem, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT  a.* 
+			FROM sys_menu AS a
+			LEFT JOIN sys_role_menu_mapping AS b ON a.sys_menu_id=b.sys_menu_id 
+			WHERE b.sys_role_id = ? AND a.menu_type = 1
+            ORDER BY sort ASC `
+	_, err = o.Raw(sql, roleId).QueryRows(&items)
+	return
+}
+
+// GetSysMenuItemsByCondition 获取菜单列表
+func GetSysMenuItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*SysMenu, err error) {
+	o := orm.NewOrm()
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM sys_menu WHERE 1=1 %s %s`, fields, condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}

+ 99 - 0
models/sys_message_report.go

@@ -0,0 +1,99 @@
+package models
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type SysMessageReport struct {
+	SysMessageReportId int       `orm:"pk" description:"消息id"`
+	UserId             int       `description:"到期用户id"`
+	ReceiveSysUserId   int       `description:"接收系统用户id"`
+	MessageType        int       `description:"消息类型"`
+	Content            string    `description:"内容"`
+	Remark             string    `description:"备注"`
+	IsRead             bool      `description:"是否已读"`
+	CreateTime         time.Time `description:"创建时间"`
+	ModifyTime         time.Time `description:"修改时间"`
+}
+
+type SysMessageReportView struct {
+	SysMessageReportId int    `orm:"pk" description:"消息id"`
+	UserId             int    `description:"到期用户id"`
+	ReceiveSysUserId   int    `description:"接收系统用户id"`
+	MessageType        int    `description:"消息类型"`
+	Content            string `description:"内容"`
+	Remark             string `description:"备注"`
+	IsRead             bool   `description:"是否已读"`
+	CreateTime         string `description:"创建时间"`
+	ModifyTime         string `description:"修改时间"`
+}
+
+func (s *SysMessageReport) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(s, cols...)
+	return
+}
+
+func UpdateReadSysMessageReportByUserId(userId int) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE sys_message_report SET is_read=1 WHERE receive_sys_user_id=? AND is_read=0`
+	_, err = o.Raw(sql, userId).Exec()
+	return
+}
+
+func GetSysMessageReportCountBySysUserId(sysUserId int) (count int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT COUNT(*) AS count FROM sys_message_report WHERE receive_sys_user_id=?`
+	err = o.Raw(sql, sysUserId).QueryRow(&count)
+	return
+}
+
+func GetSysMessageReportBySysUserId(sysUserId, startSize, pageSize int) (item []*SysMessageReport, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_message_report WHERE receive_sys_user_id=? ORDER BY is_read ASC, create_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, sysUserId, startSize, pageSize).QueryRows(&item)
+	return
+}
+
+func InsertMultiSysMessageReport(sysMessageReportList []*SysMessageReport) (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(500, sysMessageReportList)
+	return
+}
+
+func GetSysMessageReportListById(sysMessageReportIds []int) (items []*SysMessageReport, err error) {
+	o := orm.NewOrm()
+	_, err = o.QueryTable(&SysMessageReport{}).
+		Filter("sys_message_report_id__in", sysMessageReportIds).
+		All(&items)
+	return
+}
+
+func GetSysMessageReportCount(sysUserId, userId, messageType int) (count int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT COUNT(*) AS count FROM sys_message_report WHERE user_id=? AND message_type=? `
+	err = o.Raw(sql, userId, messageType).QueryRow(&count)
+	return
+}
+
+func GetSysMessageReportByCondition(condition string, pars []interface{}) (items []*SysMessageReport, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_message_report WHERE 1=1`
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	return
+}
+
+func GetSysMessageReportCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT COUNT(*) AS count FROM sys_message_report `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars...).QueryRow(&count)
+	return
+}

+ 120 - 0
models/sys_role.go

@@ -0,0 +1,120 @@
+package models
+
+import (
+	"context"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type SysRole struct {
+	SysRoleId   int       `orm:"pk" description:"角色id"`
+	SysRoleName string    `description:"姓名"`
+	CreateTime  time.Time `description:"创建时间"`
+	ModifyTime  time.Time `description:"更新时间"`
+}
+
+func (s *SysRole) Add() (err error) {
+	o := orm.NewOrm()
+	_, err = o.Insert(s)
+	return
+}
+
+func DeleteSysRoleById(roleId int) (err error) {
+	sql := `DELETE FROM sys_role WHERE sys_role_id=? `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, roleId).Exec()
+	return
+}
+
+func GetSysRoleByRoleName(roleName string) (item *SysRole, err error) {
+	sql := `SELECT * FROM sys_role WHERE sys_role_name=? `
+	o := orm.NewOrm()
+	err = o.Raw(sql, roleName).QueryRow(&item)
+	return
+}
+
+func GetSysRoleCountByRoleName(roleName string) (count int, err error) {
+	sql := `SELECT COUNT(1) AS count FROM sys_role WHERE sys_role_name=? `
+	o := orm.NewOrm()
+	err = o.Raw(sql, roleName).QueryRow(&count)
+	return
+}
+
+func UpdateSysUserRoleByRoleId(roleId int, roleName string) (err error) {
+	o := orm.NewOrm()
+	err = o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
+		role := &SysRole{}
+		role.SysRoleId = roleId
+		role.SysRoleName = roleName
+		role.ModifyTime = time.Now()
+		_, e := txOrm.Update(role, "sys_role_id", "sys_role_name", "modify_time")
+		if e != nil {
+			return e
+		}
+		sql := `UPDATE sys_user SET sys_role_name=? WHERE sys_role_id=?`
+		_, e = txOrm.Raw(sql, roleName, roleId).Exec()
+		return e
+	})
+	return
+}
+
+func GetSysRoleById(id int) (item *SysRole, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_role WHERE sys_role_id=?`
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func GetSysRoleListCount() (count int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT COUNT(1) AS count FROM sys_role `
+	err = o.Raw(sql).QueryRow(&count)
+	return
+}
+
+func GetSysRoleList(startSize, pageSize int) (items []*SysRole, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_role ORDER BY modify_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetSysRoleListNoPage() (items []*SysRole, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_role ORDER BY modify_time DESC `
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// CreateMultiSysRoleMenu 删除并新增角色权限
+func CreateMultiSysRoleMenu(roleId int, items []*SysRoleMenuMapping) (err error) {
+	if roleId == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	err = o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
+		sql := `DELETE FROM sys_role_menu_mapping WHERE sys_role_id = ?`
+		_, e := txOrm.Raw(sql, roleId).Exec()
+		if e != nil {
+			return e
+		}
+
+		if len(items) > 0 {
+			_, e = txOrm.InsertMulti(len(items), items)
+			if e != nil {
+				return e
+			}
+		}
+		return nil
+	})
+	return
+}
+
+// GetSysRoleMenuByRoleId 获取角色关联菜单
+func GetSysRoleMenuByRoleId(roleId int) (items []*SysRoleMenuMapping, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM sys_role_menu_mapping WHERE sys_role_id = ?`
+	_, err = o.Raw(sql, roleId).QueryRows(&items)
+	return
+}

+ 39 - 0
models/sys_session.go

@@ -0,0 +1,39 @@
+package models
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type SysSession struct {
+	SysSessionId    int `orm:"pk"`
+	SysUserId       int
+	UserName        string
+	AccessToken     string
+	ExpiredTime     time.Time
+	CreatedTime     time.Time
+	LastUpdatedTime time.Time
+}
+
+func (s *SysSession) AddSysSession() (err error) {
+	o := orm.NewOrm()
+	insertId, err := o.Insert(s)
+	s.SysSessionId = int(insertId)
+	return
+}
+
+func GetSysSessionByToken(token string) (item *SysSession, err error) {
+	sql := `SELECT * FROM sys_session WHERE access_token=? AND expired_time> NOW() ORDER BY expired_time DESC LIMIT 1 `
+	o := orm.NewOrm()
+	err = o.Raw(sql, token).QueryRow(&item)
+	return
+}
+
+// ExpiredSysSessionBySysUserId 过期掉用户token
+func ExpiredSysSessionBySysUserId(sysUserId int) (err error) {
+	sql := `UPDATE sys_session SET expired_time = NOW()  WHERE sys_user_id=? AND expired_time > NOW()`
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, sysUserId).Exec()
+	return
+}

+ 166 - 0
models/sys_user.go

@@ -0,0 +1,166 @@
+package models
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type SysUser struct {
+	SysUserId        int       `orm:"pk" description:"系统用户id"`
+	SysUserName      string    `description:"账号"`
+	SysRealName      string    `description:"姓名"`
+	Password         string    `description:"密码"`
+	Email            string    `description:"邮箱"`
+	Phone            string    `description:"手机号"`
+	AreaCode         string    `description:"手机区号"`
+	SysRoleId        int       `description:"角色id"`
+	SysRoleName      string    `description:"角色名称"`
+	SysDepartmentId  int       `description:"所属部门id"`
+	SysDepartmentId1 int       `description:"所属部门一级id"`
+	SysDepartmentId2 int       `description:"所属部门二级id"`
+	SysDepartmentId3 int       `description:"所属部门三级id"`
+	Province         string    `description:"省"`
+	City             string    `description:"市"`
+	IsEnabled        bool      `description:"是否启用"`
+	CreateTime       time.Time `description:"创建时间"`
+	ModifyTime       time.Time `description:"更新时间"`
+}
+
+type SysUserView struct {
+	SysUserId         int       `orm:"pk" description:"系统用户id"`
+	SysUserName       string    `description:"账号"`
+	SysRealName       string    `description:"姓名"`
+	Password          string    `description:"密码"`
+	Email             string    `description:"邮箱"`
+	Phone             string    `description:"手机号"`
+	AreaCode          string    `description:"手机区号"`
+	SysRoleId         int       `description:"角色id"`
+	SysRoleName       string    `description:"角色名称"`
+	SysDepartmentId   int       `description:"所属部门id"`
+	SysDepartmentName string    `description:"所属部门全路径"`
+	Province          string    `description:"省"`
+	City              string    `description:"市"`
+	IsEnabled         bool      `description:"是否启用"`
+	CreateTime        time.Time `description:"创建时间"`
+	ModifyTime        time.Time `description:"更新时间"`
+}
+
+type SysUserMapping struct {
+	SysUserId          int       `orm:"pk" description:"系统用户id"`
+	SysUserName        string    `description:"账号"`
+	SysRealName        string    `description:"姓名"`
+	Password           string    `description:"密码"`
+	Email              string    `description:"邮箱"`
+	Phone              string    `description:"手机号"`
+	AreaCode           string    `description:"手机区号"`
+	SysRoleId          int       `description:"角色id"`
+	SysRoleName        string    `description:"角色名称"`
+	SysDepartmentId    int       `description:"所属部门id"`
+	SysDepartmentName1 string    `description:"所属部门一级名称"`
+	SysDepartmentName2 string    `description:"所属部门二级名称"`
+	SysDepartmentName3 string    `description:"所属部门三级名称"`
+	Province           string    `description:"省"`
+	City               string    `description:"市"`
+	IsEnabled          bool      `description:"是否启用"`
+	CreateTime         time.Time `description:"创建时间"`
+	ModifyTime         time.Time `description:"更新时间"`
+}
+
+func (s *SysUser) Save() (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertOrUpdate(s, "sys_user_id")
+	return
+}
+
+func (s *SysUser) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(s, cols...)
+	return
+}
+
+func SaveSysUser(sysUser *SysUser, sysDepartmendPathIds []int) (err error) {
+	o := orm.NewOrm()
+	sysUser.SysDepartmentId1 = sysDepartmendPathIds[0]
+	sysUser.SysDepartmentId2 = sysDepartmendPathIds[1]
+	sysUser.SysDepartmentId3 = sysDepartmendPathIds[2]
+	_, err = o.InsertOrUpdate(sysUser)
+	return
+}
+
+func GetSysUserBySysUserName(sysUserName string) (item *SysUser, err error) {
+	sql := `SELECT * FROM sys_user WHERE sys_user_name=?`
+	o := orm.NewOrm()
+	err = o.Raw(sql, sysUserName).QueryRow(&item)
+	return
+}
+
+func GetSysUserByDepartmentId(sysDepartmentId int, enabled bool) (items []*SysUser, err error) {
+	sql := `SELECT * FROM sys_user WHERE sys_department_id=? AND is_enabled=?`
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, sysDepartmentId, enabled).QueryRows(&items)
+	return
+}
+
+func GetSysUserById(sysUserId int) (item *SysUser, err error) {
+	sql := `SELECT * FROM sys_user WHERE sys_user_id=?`
+	o := orm.NewOrm()
+	err = o.Raw(sql, sysUserId).QueryRow(&item)
+	return
+}
+
+func GetSysUserCountById(sysUserId int) (count int, err error) {
+	sql := `SELECT COUNT(1) AS count FROM sys_user WHERE sys_user_id=?`
+	o := orm.NewOrm()
+	err = o.Raw(sql, sysUserId).QueryRow(&count)
+	return
+}
+
+func GetSysUserCountBySysUserName(sysUserName string) (count int, err error) {
+	sql := `SELECT COUNT(1) AS count FROM sys_user WHERE sys_user_name=?`
+	o := orm.NewOrm()
+	err = o.Raw(sql, sysUserName).QueryRow(&count)
+	return
+}
+
+func GetSysUserCount(condition string, pars []interface{}) (count int, err error) {
+	sql := `SELECT COUNT(1) AS count FROM sys_user WHERE 1=1`
+	if condition != "" {
+		sql += condition
+	}
+	o := orm.NewOrm()
+	err = o.Raw(sql, pars...).QueryRow(&count)
+	return
+}
+
+func GetSysUserCountByRoleId(roleId int) (count int, err error) {
+	sql := `SELECT COUNT(1) AS count FROM sys_user WHERE sys_role_id=?`
+	o := orm.NewOrm()
+	err = o.Raw(sql, roleId).QueryRow(&count)
+	return
+}
+
+func GetSysUserListByCondition(condition string, pars []interface{}, startSize, pageSize int) (items []*SysUser, err error) {
+	sql := `SELECT * FROM sys_user u WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY u.is_enabled DESC, u.modify_time DESC LIMIT ?,?`
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetSysUserListByIsEnabled(isEnabled bool) (items []*SysUser, err error) {
+	sql := `SELECT * FROM sys_user u WHERE 1=1 AND is_enabled=?`
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, isEnabled).QueryRows(&items)
+	return
+}
+
+func GetSysUserIdList() (items []int, err error) {
+	sql := ` SELECT sys_user_id FROM sys_user WHERE 1=1 `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}

+ 325 - 0
models/user.go

@@ -0,0 +1,325 @@
+package models
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type User struct {
+	UserId         int       `orm:"pk" description:"用户id"`
+	RealName       string    `description:"姓名"`
+	Phone          string    `description:"手机号"`
+	AreaCode       string    `description:"区号"`
+	Email          string    `description:"邮箱"`
+	SellerId       int       `description:"销售id(SysUserId)"`
+	Company        string    `description:"所属公司"`
+	ValidStartTime time.Time `description:"有效期开始时间"`
+	ValidEndTime   time.Time `description:"有效期结束时间"`
+	Status         int       `description:"用户类型: 0表示禁用,1表示潜在客户,2表示正式客户"`
+	CreateTime     time.Time `description:"系统中首次新增用户的时间"`
+	ModifyTime     time.Time `description:"系统中用户信息变更的时间"`
+	RegisterTime   time.Time `description:"用户首次登录小程序的时间"`
+	IsSubscribed   bool      `description:"是否关注公众号: 0表示没有关注,1表示关注"`
+	IsRegistered   bool      `description:"是否注册: 0表示没有注册,1表示注册"`
+}
+
+type UserView struct {
+	UserId         int    `orm:"pk" description:"用户id"`
+	RealName       string `description:"姓名"`
+	Phone          string `description:"手机号"`
+	AreaCode       string `description:"区号"`
+	Email          string `description:"邮箱"`
+	SellerId       int    `description:"销售id(SysUserId)"`
+	SellerName     string `description:"销售姓名"`
+	Company        string `description:"所属公司"`
+	ValidStartTime string `description:"有效期开始时间"`
+	ValidEndTime   string `description:"有效期结束时间"`
+	RestDate       int    `description:"剩余天数"`
+	Status         int    `description:"用户类型: 0表示禁用,1表示潜在客户,2表示正式客户"`
+	ReadCnt        int    `description:"用户阅读量"`
+	CreateTime     string `description:"系统中首次新增用户的时间"`
+	ModifyTime     string `description:"系统中用户信息变更的时间"`
+	LastUpdateTime string `description:"最近一次阅读时间"`
+	RegisterTime   string `description:"用户首次登录小程序的时间"`
+	IsSubscribed   bool   `description:"是否关注公众号: 0表示没有关注,1表示关注"`
+	IsRegistered   bool   `description:"是否注册: 0表示没有注册,1表示注册"`
+}
+
+func (u *User) Save() (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertOrUpdate(u)
+	return
+}
+
+func (u *User) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(u, cols...)
+	return
+}
+
+func UpdateUserStatus(condition string, pars []interface{}) (err error) {
+	o := orm.NewOrm()
+	sql := ` UPDATE user SET status=0 WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).Exec()
+	return
+}
+
+func GetUserIdListByCondition(condition string, pars []interface{}) (items []int, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT user_id FROM user WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func SaveUser(user *User, chartPermissionIds []int) (err error) {
+	o := orm.NewOrm()
+	err = o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
+		insertId, er := txOrm.InsertOrUpdate(user)
+		if er != nil {
+			return er
+		}
+		if user.UserId != 0 {
+			insertId = int64(user.UserId)
+		} else {
+			user.UserId = int(insertId)
+		}
+		// 先删除再增加
+		sql := `DELETE FROM user_chart_permission_mapping WHERE user_id=?`
+		_, er = txOrm.Raw(sql, insertId).Exec()
+		if er != nil {
+			return er
+		}
+		for _, id := range chartPermissionIds {
+			userChartPermissionMapping := new(UserChartPermissionMapping)
+			userChartPermissionMapping.UserId = int(insertId)
+			userChartPermissionMapping.ChartPermissionId = id
+			_, er = txOrm.Insert(userChartPermissionMapping)
+			if er != nil {
+				return er
+
+			}
+		}
+		return nil
+	})
+	return
+}
+
+func GetUserByPhone(phone, areaCode string) (item *User, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM user WHERE phone=? AND area_code=?`
+	err = o.Raw(sql, phone, areaCode).QueryRow(&item)
+	return
+}
+
+func GetUserByEmail(email string) (item *User, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM user WHERE email=? `
+	err = o.Raw(sql, email).QueryRow(&item)
+	return
+}
+
+func GetUserById(userId int) (item *User, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM user WHERE user_id=? `
+	err = o.Raw(sql, userId).QueryRow(&item)
+	return
+}
+
+func GetUserViewById(userId int) (item *UserView, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM user WHERE user_id=? `
+	err = o.Raw(sql, userId).QueryRow(&item)
+	return
+}
+
+func GetUserList(condition string, pars []interface{}, startSize, pageSize int) (items []*UserView, err error) {
+	sql := `SELECT u.*, su.sys_real_name AS seller_name FROM user AS u
+	LEFT JOIN sys_user AS su
+	ON u.seller_id = su.sys_user_id
+	WHERE 1=1 AND (u.phone IS NOT NULL OR u.email IS NOT NULL) `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY modify_time DESC LIMIT ?,? `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetUserListByConditonSort(condition, sortConditon string, pars []interface{}, startSize, pageSize int) (items []*UserView, err error) {
+	sql := `SELECT u.*, su.sys_real_name AS seller_name
+	FROM user AS u
+	LEFT JOIN sys_user AS su
+	ON u.seller_id = su.sys_user_id
+	WHERE 1=1 AND (u.phone IS NOT NULL OR u.email IS NOT NULL) `
+	if condition != "" {
+		sql += condition
+	}
+	if sortConditon != "" {
+		sql += sortConditon
+	}
+	sql += ` LIMIT ?,? `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetPotentialUserCountByConditon(condition string, pars []interface{}) (count int, err error) {
+	sql := `SELECT COUNT(DISTINCT u.user_id) AS count
+	FROM user AS u
+	LEFT JOIN user_read_record AS ur
+	ON u.user_id = ur.user_id
+	WHERE 1=1 AND (u.phone IS NOT NULL OR u.email IS NOT NULL) AND u.status=1`
+	if condition != "" {
+		sql += condition
+	}
+	o := orm.NewOrm()
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetPotentialUserCountByConditonV2(condition string, pars []interface{}) (count int, err error) {
+	sql := `SELECT COUNT(u.user_id) AS count
+	FROM user AS u
+	LEFT JOIN (
+		SELECT user_id, MAX(create_time) AS create_time
+		FROM user_read_record
+		GROUP BY user_id
+	) AS ur
+	ON u.user_id = ur.user_id
+	WHERE 1=1 AND (u.phone IS NOT NULL OR u.email IS NOT NULL) AND u.status=1`
+	if condition != "" {
+		sql += condition
+	}
+	o := orm.NewOrm()
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetPotentialUserIdsByConditonV2(condition string, pars []interface{}, sortConditon string, startSize, pageSize int) (items []*UserView, err error) {
+	sql := `SELECT DISTINCT u.*, ur.read_cnt, ur.create_time AS last_update_time
+	FROM user AS u
+	LEFT JOIN (
+		SELECT user_id, MAX(create_time) AS create_time, COUNT(user_id) AS read_cnt
+		FROM user_read_record
+		GROUP BY user_id
+	) AS ur
+	ON u.user_id = ur.user_id
+	WHERE 1=1 AND (u.phone IS NOT NULL OR u.email IS NOT NULL) AND u.status=1`
+	if condition != "" {
+		sql += condition
+	}
+	if sortConditon != "" {
+		sql += sortConditon
+	}
+	sql += ` LIMIT ?,? `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetPotentialUserIdsByConditon(condition string, pars []interface{}) (userIds []string, err error) {
+	sql := `SELECT DISTINCT u.user_id AS user_id
+	FROM user AS u
+	LEFT JOIN user_read_record AS ur
+	ON u.user_id = ur.user_id
+	WHERE 1=1 AND (u.phone IS NOT NULL OR u.email IS NOT NULL) AND u.status=1`
+	if condition != "" {
+		sql += condition
+	}
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, pars).QueryRows(&userIds)
+	return
+}
+
+func GetPotentialUserListByConditonSort(userIds []string, sortConditon string, startSize, pageSize int) (items []*UserView, err error) {
+	sql := `SELECT u.*, COUNT(ur.user_id) AS read_cnt, Max(ur.create_time) AS last_update_time
+	FROM user AS u
+	LEFT JOIN user_read_record AS ur
+	ON u.user_id = ur.user_id
+	WHERE 1=1 AND (u.phone IS NOT NULL OR u.email IS NOT NULL) AND u.status=1`
+	if len(userIds) > 0 {
+		sql += fmt.Sprintf(" AND u.user_id IN (%s)", strings.Join(userIds, ","))
+	}
+	sql += ` GROUP BY u.user_id`
+	if sortConditon != "" {
+		sql += sortConditon
+	}
+	sql += ` LIMIT ?,? `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetUserReadList(condition, sortCondition string, pars []interface{}, startSize, pageSize int) (items []*UserView, err error) {
+	sql := `SELECT u.*, su.sys_real_name AS seller_name, COUNT(ur.user_id) AS read_cnt, Max(ur.create_time) AS last_update_time
+	FROM user AS u
+	LEFT JOIN sys_user AS su
+	ON u.seller_id = su.sys_user_id
+	LEFT JOIN user_read_record AS ur
+	ON u.user_id = ur.user_id
+	WHERE 1=1 AND (u.phone IS NOT NULL OR u.email IS NOT NULL) `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` GROUP BY u.user_id `
+	if sortCondition != "" {
+		sql += sortCondition
+	} else {
+		sql += ` ORDER BY read_cnt DESC `
+	}
+	sql += ` LIMIT ?,? `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetUserReadCount(condition string, pars []interface{}) (count int, err error) {
+	sql := `SELECT COUNT(*) AS count 
+	FROM user  AS u
+	WHERE 1=1 AND (u.phone IS NOT NULL OR u.email IS NOT NULL) `
+	if condition != "" {
+		sql += condition
+	}
+	o := orm.NewOrm()
+	err = o.Raw(sql, pars...).QueryRow(&count)
+	return
+}
+
+func GetUserCount(condition string, pars []interface{}) (count int, err error) {
+	sql := `SELECT COUNT(*) AS count FROM user AS u WHERE 1=1 AND (u.phone IS NOT NULL OR u.email IS NOT NULL) `
+	if condition != "" {
+		sql += condition
+	}
+	o := orm.NewOrm()
+	err = o.Raw(sql, pars...).QueryRow(&count)
+	return
+}
+
+func DeleteUserById(userId int) (err error) {
+	o := orm.NewOrm()
+	err = o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
+		sql := `DELETE FROM user WHERE user_id=?`
+		_, e := txOrm.Raw(sql, userId).Exec()
+		if e != nil {
+			return e
+		}
+		sql = `DELETE FROM user_chart_permission_mapping WHERE user_id=?`
+		_, e = txOrm.Raw(sql, userId).Exec()
+		if e != nil {
+			return e
+		}
+		return nil
+	})
+	return
+}

+ 41 - 0
models/user_change_record.go

@@ -0,0 +1,41 @@
+package models
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type UserChangeRecord struct {
+	UserChangeRecordId int       `orm:"pk" description:"id"`
+	UserId             int       `description:"用户id"`
+	SysUserId          int       `description:"系统用户id"`
+	Content            string    `description:"内容"`
+	CreateTime         time.Time `description:"创建时间"`
+}
+
+func (u *UserChangeRecord) Insert() (err error) {
+	u.CreateTime = time.Now()
+	o := orm.NewOrm()
+	_, err = o.Insert(u)
+	return
+}
+func UserChangeRecordMultiInsert(list []*UserChangeRecord) (err error) {
+	o := orm.NewOrm()
+	_, err = o.InsertMulti(500, list)
+	return
+}
+
+func GetUserChangeRecordListById(userId int) (items []*UserChangeRecord, err error) {
+	sql := `SELECT * FROM user_change_record WHERE user_id=? ORDER BY create_time DESC `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, userId).QueryRows(&items)
+	return
+}
+
+func GetUserChangeRecordCount() (count int, err error) {
+	sql := `SELECT COUNT(*) AS count FROM user_change_record `
+	o := orm.NewOrm()
+	err = o.Raw(sql).QueryRow(&count)
+	return
+}

+ 16 - 0
models/user_chart_permission_mapping.go

@@ -0,0 +1,16 @@
+package models
+
+import "github.com/beego/beego/v2/client/orm"
+
+type UserChartPermissionMapping struct {
+	UserChartPermissionMappingId int `orm:"pk" description:"id"`
+	UserId                       int `description:"用户id"`
+	ChartPermissionId            int `description:"品种id"`
+}
+
+func GetChartPermissionIdByUserId(UserId int) (items []int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT chart_permission_id FROM user_chart_permission_mapping WHERE user_id=?`
+	_, err = o.Raw(sql, UserId).QueryRows(&items)
+	return
+}

+ 98 - 0
models/user_read_record.go

@@ -0,0 +1,98 @@
+package models
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type UserReadRecord struct {
+	UserReadRecordId int    `orm:"pk" description:"id"`
+	UserId           int    `description:"用户id"`
+	ReportId         int    `description:"报告id"`
+	ReportTitle      string `description:"报告标题"`
+	// ChartPermissionId1  string    `description:"一级品种id"`
+	// ChartPermissionId2  string    `description:"二级品种id"`
+	ChartPermissionName string    `description:"二级品种名称"`
+	ClassifyId1         int       `description:"一级级分类id"`
+	ClassifyName1       string    `description:"一级分类名称"`
+	ClassifyId2         int       `description:"二级分类id"`
+	ClassifyName2       string    `description:"二级分类名称"`
+	Timestamp           int       `description:"阅读开始时间戳"`
+	EndTimestamp        int       `description:"阅读结束时间戳"`
+	CreateTime          time.Time `description:"创建时间"`
+	CreateDate          string    `description:"创建日期"`
+	StayTime            string    `description:"停留时间"`
+	StayTimestamp       string    `description:"停留时间戳"`
+}
+
+type ReadCntStaitc struct {
+	CreateDate string
+	Count      int
+}
+type PermissionCntStaitc struct {
+	ChartPermissionId int
+	PermissionName    string
+	Count             int
+	Percent           float64
+}
+
+func GetUserReadRecordByUserId(userId int, condition string, pars []interface{}, startSize, pageSize int) (items []*UserReadRecord, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT DISTINCT ur.user_read_record_id, ur.report_id, ur.report_title, ur.chart_permission_name, ur.classify_name2, 
+		ur.create_time, ur.stay_time, ur.classify_id2
+	  	FROM user_read_record AS ur
+		LEFT JOIN user_read_permission2 AS urp2
+		ON ur.user_read_record_id = urp2.user_read_record_id
+		WHERE user_id = ? `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY create_time DESC LIMIT ?, ?`
+	_, err = o.Raw(sql, userId, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetUserReadRecordCountByUserId(userId int, condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT COUNT(DISTINCT ur.user_read_record_id) AS count
+	  	FROM user_read_record AS ur
+		LEFT JOIN user_read_permission2 AS urp2
+		ON ur.user_read_record_id = urp2.user_read_record_id
+		WHERE user_id = ? `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, userId, pars).QueryRow(&count)
+	return
+}
+
+func GetStaticReadCnt(condition string, pars []interface{}, startDate, endDate string) (items []*ReadCntStaitc, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT create_date, COUNT(*) AS count
+	FROM user_read_record AS ur
+	LEFT JOIN user_read_permission2 AS urp
+	ON ur.user_read_record_id = urp.user_read_record_id
+	WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` AND (ur.create_date BETWEEN ? AND ?)  GROUP BY ur.create_date`
+	_, err = o.Raw(sql, pars, startDate, endDate).QueryRows(&items)
+	return
+}
+
+func GetStaticPermissionCnt(condition string, pars []interface{}) (items []*PermissionCntStaitc, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT urp.chart_permission_id, urp.permission_name,COUNT(*) AS count
+	FROM user_read_permission1 AS urp
+	LEFT JOIN user_read_record AS ur
+	ON urp.user_read_record_id = ur.user_read_record_id
+	WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` GROUP BY urp.chart_permission_id`
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}

+ 397 - 0
routers/commentsRouter.go

@@ -0,0 +1,397 @@
+package routers
+
+import (
+	beego "github.com/beego/beego/v2/server/web"
+	"github.com/beego/beego/v2/server/web/context/param"
+)
+
+func init() {
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:ClassifyController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SellerController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SellerController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysDepartmentController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysDepartmentController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysDepartmentController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysDepartmentController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysDepartmentController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysDepartmentController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysDepartmentController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysDepartmentController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysDepartmentController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysDepartmentController"],
+        beego.ControllerComments{
+            Method: "SetSort",
+            Router: `/set_sort`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysMessageReportController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysMessageReportController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysMessageReportController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysMessageReportController"],
+        beego.ControllerComments{
+            Method: "Read",
+            Router: `/read`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"],
+        beego.ControllerComments{
+            Method: "AllList",
+            Router: `/allList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"],
+        beego.ControllerComments{
+            Method: "SysRoleMenuAuthList",
+            Router: `/menu/auth_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"],
+        beego.ControllerComments{
+            Method: "SysRoleMenuAuthSave",
+            Router: `/menu/auth_save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"],
+        beego.ControllerComments{
+            Method: "ButtonList",
+            Router: `/menu/buttons`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysRoleController"],
+        beego.ControllerComments{
+            Method: "SysMenuList",
+            Router: `/menu/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"],
+        beego.ControllerComments{
+            Method: "EditEnabled",
+            Router: `/editEnabled`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"],
+        beego.ControllerComments{
+            Method: "MoveToDepartment",
+            Router: `/moveToDepartment`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"],
+        beego.ControllerComments{
+            Method: "ResetMyPass",
+            Router: `/reset_my_pass`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:SysUserController"],
+        beego.ControllerComments{
+            Method: "ResetPass",
+            Router: `/reset_pass`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "ChangeList",
+            Router: `/change_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "Check",
+            Router: `/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "EditEnabled",
+            Router: `/editEnabled`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "PotentialEdit",
+            Router: `/potential/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "PotentialList",
+            Router: `/potential/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserLoginController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserLoginController"],
+        beego.ControllerComments{
+            Method: "AreaCodeList",
+            Router: `/area_code/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserLoginController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserLoginController"],
+        beego.ControllerComments{
+            Method: "Login",
+            Router: `/login`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserReadRecordController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserReadRecordController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserReadRecordController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserReadRecordController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserReadRecordController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserReadRecordController"],
+        beego.ControllerComments{
+            Method: "ReadCntChart",
+            Router: `/readCntChart`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserReadRecordController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm/controllers:UserReadRecordController"],
+        beego.ControllerComments{
+            Method: "ReadPermissionChart",
+            Router: `/readPermissionChart`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+}

+ 58 - 1
routers/router.go

@@ -2,9 +2,66 @@ package routers
 
 import (
 	"eta/eta_mini_crm/controllers"
+
 	beego "github.com/beego/beego/v2/server/web"
+	"github.com/beego/beego/v2/server/web/filter/cors"
 )
 
 func init() {
-    beego.Router("/", &controllers.MainController{})
+	beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
+		AllowAllOrigins:  true,
+		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
+		AllowHeaders:     []string{"Origin", "Authorization", "Uuid", "Accesstoken", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
+		ExposeHeaders:    []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
+		AllowCredentials: true,
+	}))
+	ns := beego.NewNamespace("/adminapi",
+		beego.NSNamespace("/sys_user",
+			beego.NSInclude(
+				&controllers.UserLoginController{},
+				&controllers.SysUserController{},
+			),
+		),
+		beego.NSNamespace("/department",
+			beego.NSInclude(
+				&controllers.SysDepartmentController{},
+			),
+		),
+		beego.NSNamespace("/role",
+			beego.NSInclude(
+				&controllers.SysRoleController{},
+			),
+		),
+		beego.NSNamespace("/chart_permission",
+			beego.NSInclude(
+				&controllers.ChartPermissionController{},
+			),
+		),
+		beego.NSNamespace("/user",
+			beego.NSInclude(
+				&controllers.UserController{},
+			),
+		),
+		beego.NSNamespace("/sys_message",
+			beego.NSInclude(
+				&controllers.SysMessageReportController{},
+			),
+		),
+		beego.NSNamespace("/seller",
+			beego.NSInclude(
+				&controllers.SellerController{},
+			),
+		),
+		beego.NSNamespace("/classify",
+			beego.NSInclude(
+				&controllers.ClassifyController{},
+			),
+		),
+		beego.NSNamespace("/read",
+			beego.NSInclude(
+				&controllers.UserReadRecordController{},
+			),
+		),
+	)
+	beego.AddNamespace(ns)
 }

+ 189 - 0
scheduler/task.go

@@ -0,0 +1,189 @@
+package scheduler
+
+import (
+	"context"
+	"eta/eta_mini_crm/models"
+	"eta/eta_mini_crm/utils"
+	"fmt"
+	"time"
+
+	"github.com/beego/beego/v2/task"
+)
+
+func InitJob() {
+	fmt.Println("消息推送任务开启。。。")
+	// 每天凌晨12点10分检测, 发送消息
+	tk1 := task.NewTask("SendReminderMsg", "0 10 0 * * *", SendReminderMsg)
+	task.AddTask("发送消息提醒", tk1)
+	// 每天凌晨12点检测, 修改用户状态
+	tk2 := task.NewTask("ModifyUserStatus", "5 0 0 * * *", ModifyUserStatus)
+	task.AddTask("定时修改用户状态", tk2)
+	task.StartTask()
+}
+
+func ModifyUserStatus(ctx context.Context) (err error) {
+	defer func() {
+		if err != nil {
+			utils.ApiLog.Info("修改用户状态,定时任务出错,Err: %s", err)
+		}
+		if err := recover(); err != nil {
+			utils.ApiLog.Warn("修改用户状态,定时任务出错,Err: %s", err)
+		}
+	}()
+	curTime := time.Now()
+	var pars []interface{}
+	condition := ` AND valid_end_time<?`
+	pars = append(pars, curTime)
+	condition += ` AND status=? `
+	pars = append(pars, 2)
+	userIds, err := models.GetUserIdListByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	err = models.UpdateUserStatus(condition, pars)
+	if err != nil {
+		return
+	}
+	userRecordList := make([]*models.UserChangeRecord, 0)
+	for _, v := range userIds {
+		record := &models.UserChangeRecord{}
+		record.UserId = v
+		record.SysUserId = 0
+		record.Content = "有效期到期禁用用户"
+		record.CreateTime = time.Now()
+		userRecordList = append(userRecordList, record)
+	}
+	err = models.UserChangeRecordMultiInsert(userRecordList)
+	if err != nil {
+		return
+	}
+	return
+}
+
+func SendReminderMsg(ctx context.Context) (err error) {
+	defer func() {
+		if err != nil {
+			utils.ApiLog.Info("发送提醒消息,定时任务出错,Err: %s", err)
+		}
+		if err := recover(); err != nil {
+			utils.ApiLog.Warn("发送提醒消息,定时任务出错,Err: %s", err)
+		}
+	}()
+	var userPars7 []interface{}
+	var userPars15 []interface{}
+	var userPars30 []interface{}
+	var userPars60 []interface{}
+	var msgPars7 []interface{}
+	var msgPars15 []interface{}
+	var msgPars30 []interface{}
+	var msgPars60 []interface{}
+
+	userTime7 := time.Now().AddDate(0, 0, 7)
+	userTime15 := time.Now().AddDate(0, 0, 15)
+	userTime30 := time.Now().AddDate(0, 0, 30)
+	userTime60 := time.Now().AddDate(0, 0, 60)
+	msgTime7 := time.Now().AddDate(0, 0, -7)
+	msgTime15 := time.Now().AddDate(0, 0, -15)
+	msgTime30 := time.Now().AddDate(0, 0, -30)
+	msgTime60 := time.Now().AddDate(0, 0, -60)
+
+	userCondition1 := ` AND valid_end_time<? `
+	userCondition2 := ` AND valid_end_time<? AND valid_end_time>? `
+	msgCondition := ` AND create_time>? `
+	userPars7 = append(userPars7, userTime7)
+	userPars15 = append(userPars15, userTime15, userTime7)
+	userPars30 = append(userPars30, userTime30, userTime15)
+	userPars60 = append(userPars60, userTime60, userTime30)
+	msgPars7 = append(msgPars7, msgTime7)
+	msgPars15 = append(msgPars15, msgTime15)
+	msgPars30 = append(msgPars30, msgTime30)
+	msgPars60 = append(msgPars60, msgTime60)
+
+	err = SendMsgToSysUser(userCondition1, msgCondition, userPars7, msgPars7, 7)
+	if err != nil {
+		utils.ApiLog.Warn("发送提醒消息,定时任务出错,Err: %s", err.Error())
+	}
+	err = SendMsgToSysUser(userCondition2, msgCondition, userPars15, msgPars15, 15)
+	if err != nil {
+		utils.ApiLog.Warn("发送提醒消息,定时任务出错,Err: %s", err.Error())
+	}
+	err = SendMsgToSysUser(userCondition2, msgCondition, userPars30, msgPars30, 30)
+	if err != nil {
+		utils.ApiLog.Warn("发送提醒消息,定时任务出错,Err: %s", err.Error())
+	}
+	err = SendMsgToSysUser(userCondition2, msgCondition, userPars60, msgPars60, 60)
+	if err != nil {
+		utils.ApiLog.Warn("发送提醒消息,定时任务出错,Err: %s", err.Error())
+	}
+	return
+}
+
+func SendMsgToSysUser(userCondition, msgCondition string, userPars, msgPars []interface{}, messagetType int) (err error) {
+	total, err := models.GetUserCount(userCondition, userPars)
+	if err != nil {
+		utils.ApiLog.Warn("获取用户列表失败 task err: %s", err.Error())
+	}
+	if total == 0 {
+		return
+	}
+	// 获得待发送用户的列表
+	userList, err := models.GetUserList(userCondition, userPars, 0, total)
+	if err != nil {
+		return
+	}
+
+	// 获得待通知系统用户的列表
+	sysUserIds, err := models.GetSysUserIdList()
+	if err != nil {
+		return
+	}
+
+	messageList, err := models.GetSysMessageReportByCondition(msgCondition, msgPars)
+	if err != nil {
+		return
+	}
+	layout := "【%s】到期%d天提醒,请及时跟进"
+	messageMap := make(map[int]map[int]struct{})
+	for _, v := range messageList {
+		if mv, ok := messageMap[v.ReceiveSysUserId]; ok {
+			mv[v.UserId] = struct{}{}
+		} else {
+			messageMap[v.ReceiveSysUserId] = make(map[int]struct{})
+			messageMap[v.ReceiveSysUserId][v.UserId] = struct{}{}
+		}
+	}
+	sendMsg := make([]*models.SysMessageReport, 0)
+	for _, v := range sysUserIds {
+		if userMap, ok := messageMap[v]; ok {
+			for _, u := range userList {
+				if _, ok := userMap[u.UserId]; !ok {
+					sendMsg = append(sendMsg, &models.SysMessageReport{
+						UserId:           u.UserId,
+						ReceiveSysUserId: v,
+						MessageType:      messagetType,
+						IsRead:           false,
+						CreateTime:       time.Now(),
+						ModifyTime:       time.Now(),
+						Content:          fmt.Sprintf(layout, u.RealName, messagetType),
+					})
+				}
+			}
+		} else {
+			for _, u := range userList {
+				sendMsg = append(sendMsg, &models.SysMessageReport{
+					UserId:           u.UserId,
+					ReceiveSysUserId: v,
+					MessageType:      messagetType,
+					IsRead:           false,
+					CreateTime:       time.Now(),
+					ModifyTime:       time.Now(),
+					Content:          fmt.Sprintf(layout, u.RealName, messagetType),
+				})
+			}
+		}
+	}
+	if len(sendMsg) > 0 {
+		err = models.InsertMultiSysMessageReport(sendMsg)
+	}
+	return
+}

+ 31 - 0
services/chart_permission.go

@@ -0,0 +1,31 @@
+package services
+
+import "eta/eta_mini_crm/models"
+
+// 获得全部的权限结构列表树形结构
+func GetChartPermissionListTree(list []*models.ChartPermissionListTree, parentId int) []*models.ChartPermissionListTree {
+	res := make([]*models.ChartPermissionListTree, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			t := new(models.ChartPermissionListTree)
+			t.ChartPermissionId = v.ChartPermissionId
+			t.PermissionName = v.PermissionName
+			t.ParentId = v.ParentId
+			for _, j := range list {
+				if j.ParentId == v.ChartPermissionId {
+					c := new(models.ChartPermissionListTree)
+					c.ChartPermissionId = j.ChartPermissionId
+					c.PermissionName = j.PermissionName
+					c.ParentId = j.ParentId
+					if j.IsPublic == 1 {
+						t.PublicChild = append(t.PublicChild, c)
+					} else {
+						t.PrivateChild = append(t.PrivateChild, c)
+					}
+				}
+			}
+			res = append(res, t)
+		}
+	}
+	return res
+}

+ 75 - 0
services/sys_department.go

@@ -0,0 +1,75 @@
+package services
+
+import (
+	"eta/eta_mini_crm/models"
+	"strconv"
+	"strings"
+)
+
+// GetSysDepartmentTree 递归获取部门目录树
+func GetSysDepartmentTree(list []*models.SysDepartmentList, parentId int) []*models.SysDepartmentList {
+	res := make([]*models.SysDepartmentList, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			t := new(models.SysDepartmentList)
+			t.SysDepartmentId = v.SysDepartmentId
+			t.SysDepartmentName = v.SysDepartmentName
+			t.Level = v.Level
+			t.ParentId = v.ParentId
+			t.Child = GetSysDepartmentTree(list, v.SysDepartmentId)
+			res = append(res, t)
+		}
+	}
+	return res
+}
+
+// DeleteSysDepartmentById 根据部门id去删除部门下的所有子目录,并清除用户的关联信息
+func DeleteSysDepartmentById(sysDepartmentId, level int) (err error) {
+	sysDepartmentIds := make([]string, 0)
+	sysDepartmentIds = append(sysDepartmentIds, strconv.Itoa(sysDepartmentId))
+	var curIds = sysDepartmentIds
+	var curStringIds string
+	for i := level; i < 3; i++ {
+		// 从当前层部门依次向下遍历子部门
+		curStringIds = strings.Join(curIds, ",")
+		if curStringIds == "" {
+			break
+		}
+		sysDepartments, er := models.GetChildSysDepartmentListByIds(curStringIds)
+
+		if er != nil {
+			return er
+		}
+		curIds = make([]string, 0)
+		for _, dep := range sysDepartments {
+			if dep.SysDepartmentId == 0 {
+				continue
+			}
+			sysDepartmentIds = append(sysDepartmentIds, strconv.Itoa(dep.SysDepartmentId))
+			curIds = append(curIds, strconv.Itoa(dep.SysDepartmentId))
+		}
+	}
+	err = models.DeleteSysDepartmentByIdV2(sysDepartmentIds, level)
+	return
+}
+
+// GetSysDepartmentPathIdsById 根据当前部门id获取,部门层级列表pathIds [3]int,0索引表示1级部门id,1表示2级部门id...
+func GetSysDepartmentPathIdsById(sysDepartmentId int) (pathIds []int, err error) {
+	sysDepartment, err := models.GetSysDepartmentById(sysDepartmentId)
+	if err != nil {
+		return
+	}
+
+	pathIds = make([]int, 3)
+	pathIds[sysDepartment.Level-1] = sysDepartmentId
+	curSysDepartmentId := sysDepartment.ParentId
+	for curLevel := sysDepartment.Level - 1; curLevel >= 1; curLevel-- {
+		upSysDepartment, er := models.GetSysDepartmentListByParentId(curSysDepartmentId)
+		if er != nil {
+			return
+		}
+		curSysDepartmentId = upSysDepartment.ParentId
+		pathIds[curLevel-1] = upSysDepartment.SysDepartmentId
+	}
+	return
+}

+ 15 - 0
services/sys_menu.go

@@ -0,0 +1,15 @@
+package services
+
+import "eta/eta_mini_crm/models"
+
+// GetMenuTreeRecursive 递归菜单树
+func GetMenuTreeRecursive(list []*models.SysMenuItem, parentId int) []*models.SysMenuItem {
+	res := make([]*models.SysMenuItem, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			v.Children = GetMenuTreeRecursive(list, v.SysMenuId)
+			res = append(res, v)
+		}
+	}
+	return res
+}

+ 8 - 0
services/sys_session.go

@@ -0,0 +1,8 @@
+package services
+
+import "eta/eta_mini_crm/models"
+
+func LogoutSysUser(sysUserId int) (err error) {
+	err = models.ExpiredSysSessionBySysUserId(sysUserId)
+	return
+}

+ 45 - 0
services/sys_user.go

@@ -0,0 +1,45 @@
+package services
+
+import "eta/eta_mini_crm/models"
+
+func GetSysUserList(condition string, pars []interface{}, startSize, pageSize int) (items []*models.SysUserMapping, err error) {
+	// 查询系统用户
+	sysUserList, err := models.GetSysUserListByCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		return
+	}
+	// 建立部门id和名字的映射
+	departmentList, err := models.GetSysDepartments()
+	if err != nil {
+		return
+	}
+	departmentIdToName := make(map[int]string)
+	for _, dep := range departmentList {
+		departmentIdToName[dep.SysDepartmentId] = dep.SysDepartmentName
+	}
+	// 匹配系统用户
+	items = make([]*models.SysUserMapping, 0)
+	for _, user := range sysUserList {
+		items = append(items,
+			&models.SysUserMapping{
+				SysUserId:          user.SysUserId,
+				SysUserName:        user.SysUserName,
+				SysRealName:        user.SysRealName,
+				Email:              user.Email,
+				Phone:              user.Phone,
+				AreaCode:           user.AreaCode,
+				SysRoleId:          user.SysRoleId,
+				SysRoleName:        user.SysRoleName,
+				SysDepartmentId:    user.SysDepartmentId,
+				SysDepartmentName1: departmentIdToName[user.SysDepartmentId1],
+				SysDepartmentName2: departmentIdToName[user.SysDepartmentId2],
+				SysDepartmentName3: departmentIdToName[user.SysDepartmentId3],
+				Province:           user.Province,
+				City:               user.City,
+				IsEnabled:          user.IsEnabled,
+				CreateTime:         user.CreateTime,
+				ModifyTime:         user.ModifyTime,
+			})
+	}
+	return
+}

+ 19 - 0
services/user.go

@@ -0,0 +1,19 @@
+package services
+
+import "eta/eta_mini_crm/models"
+
+func GetUserPermissionById(userId int) (items []*models.ChartPermissionView, err error) {
+	permissionIds, err := models.GetChartPermissionIdByUserId(userId)
+	if err != nil {
+		return
+	}
+	if len(permissionIds) == 0 {
+		return
+	}
+	items, err = models.GetChartPermissionListByIds(permissionIds)
+	if err != nil {
+		return
+	}
+
+	return
+}

+ 66 - 0
utils/common.go

@@ -0,0 +1,66 @@
+package utils
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"regexp"
+	"strconv"
+)
+
+func MD5(data string) string {
+	m := md5.Sum([]byte(data))
+	return hex.EncodeToString(m[:])
+}
+
+func CheckPwd(pwd string) bool {
+	compile := `([0-9a-z]+){6,12}|(a-z0-9]+){6,12}`
+	reg := regexp.MustCompile(compile)
+	flag := reg.MatchString(pwd)
+	return flag
+}
+
+// 校验邮箱格式
+func ValidateEmailFormatat(email string) bool {
+	reg := regexp.MustCompile(RegularEmail)
+	return reg.MatchString(email)
+}
+
+// 验证是否是手机号
+func ValidateMobileFormatat(mobileNum string) bool {
+	reg := regexp.MustCompile(RegularMobile)
+	return reg.MatchString(mobileNum)
+}
+
+// 计算分页起始页
+func StartIndex(page, pagesize int) int {
+	if page > 1 {
+		return (page - 1) * pagesize
+	}
+	return 0
+}
+
+// GetLikeKeywordPars 获取sql查询中的参数切片
+func GetLikeKeywordPars(pars []interface{}, keyword string, num int) (newPars []interface{}) {
+	newPars = pars
+	if newPars == nil {
+		newPars = make([]interface{}, 0)
+	}
+	for i := 1; i <= num; i++ {
+		newPars = append(newPars, `%`+keyword+`%`)
+	}
+	return
+}
+
+func StringsToJSON(str string) string {
+	rs := []rune(str)
+	jsons := ""
+	for _, r := range rs {
+		rint := int(r)
+		if rint < 128 {
+			jsons += string(r)
+		} else {
+			jsons += "\\u" + strconv.FormatInt(int64(rint), 16) // json
+		}
+	}
+	return jsons
+}

+ 71 - 0
utils/config.go

@@ -0,0 +1,71 @@
+package utils
+
+import (
+	"fmt"
+
+	beeLogger "github.com/beego/bee/v2/logger"
+	"github.com/beego/beego/v2/server/web"
+)
+
+// 数据库配置
+var (
+	RunMode          string // 运行模式
+	MYSQL_URL_MASTER string // 数据库地址
+	MYSQL_URL_RDDP   string // 数据库地址
+
+	REDIS_CACHE string      //缓存地址
+	Rc          RedisClient //redis缓存
+)
+
+var (
+	ApiLogPath string // 接口请求地址和接口返回值日志存放地址
+	ApiLogFile string
+	BinLogPath string // 数据库相关的日志存放地址
+	BinLogFile string
+	LogMaxDays int // 日志最大保留天数
+)
+
+var DesKey string // 接口返回加密KEY
+
+func init() {
+	tmpRunMode, err := web.AppConfig.String("run_mode")
+	if err != nil {
+		panic(any("配置文件读取run_mode错误 " + err.Error()))
+	}
+	RunMode = tmpRunMode
+	fmt.Println("RunMode:", RunMode)
+	if RunMode == "" {
+		RunMode = "debug"
+		configPath := `/home/code/config/eta_mini_crm/conf/app.conf`
+		fmt.Println("configPath:", configPath)
+		err = web.LoadAppConfig("ini", configPath)
+		if err != nil {
+			fmt.Println("web.LoadAppConfig Err:" + err.Error())
+		}
+	}
+
+	config, err := web.AppConfig.GetSection(RunMode)
+	if err != nil {
+		panic(any("配置文件读取错误 " + err.Error()))
+	}
+	beeLogger.Log.Info(RunMode + " 模式")
+	MYSQL_URL_RDDP = config["mysql_url_rddp"]
+	MYSQL_URL_MASTER = config["mysql_url_master"]
+
+	REDIS_CACHE = config["beego_cache"]
+	if len(REDIS_CACHE) <= 0 {
+		panic(any("redis链接参数没有配置"))
+	}
+	// 接口返回加密KEY
+	DesKey = config["des_key"]
+
+	// 初始化缓存
+	redisClient, err := initRedis(config["redis_type"], config["beego_cache"])
+	if err != nil {
+		fmt.Println("redis链接异常:", err)
+		panic(any("redis链接参数没有配置"))
+	}
+
+	Rc = redisClient
+
+}

+ 64 - 0
utils/constants.go

@@ -0,0 +1,64 @@
+package utils
+
+const (
+	UserLoginSalt = "MiQM9YUdf89T2uIH"         // 用户登录盐值
+	DesKeySalt    = "MxuqSoUrTAmyRd9fb0TtlrPk" // DesKey盐值
+)
+
+const (
+	CACHE_ACCESS_TOKEN_LOGIN          = "pc_eta_min_crm:login:"          //管理后台登录
+	CACHE_ACCESS_TOKEN_LOGIN_NO_TRUST = "pc_eta_min_crm:login:no_trust:" //管理后台登录(不可信登录态)
+)
+
+// 手机号,电子邮箱正则
+const (
+	RegularMobile = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0-9])|(17[0-9])|(16[0-9])|(19[0-9]))\\d{8}$" //手机号码
+	RegularEmail  = `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`                                             //匹配电子邮箱
+)
+
+// 常量定义
+const (
+	FormatTime                 = "15:04:05"                //时间格式
+	FormatTimeHm               = "15:04"                   //时间格式
+	FormatDate                 = "2006-01-02"              //日期格式
+	FormatDateUnSpace          = "20060102"                //日期格式
+	FormatDateTime             = "2006-01-02 15:04:05"     //完整时间格式
+	HlbFormatDateTime          = "2006-01-02_15:04:05.999" //完整时间格式
+	FormatDateTimeUnSpace      = "20060102150405"          //完整时间格式
+	FormatShortDateTimeUnSpace = "060102150405"            //省去开头两位年份的时间格式
+	EmptyDateTimeStr           = "0000-00-00 00:00:00"     //DateTime零值字符串
+	EmptyDateStr               = "0000-00-00"              //Date零值字符串
+	FormatMonthDayUnSpace      = "0102"                    //日期格式
+	FormatMonthDay             = "01-02"                   //日期格式
+	FormatYearMonthDate        = "2006-01"                 //日期格式
+	FormatYearDate             = "2006"                    //日期格式
+	PageSize15                 = 15                        //列表页每页数据量
+	PageSize5                  = 5
+	PageSize10                 = 10
+	PageSize20                 = 20
+	PageSize30                 = 30
+	PageSize50                 = 50
+	PageSize100                = 100
+	MaxDepartmentLevel         = 3
+)
+
+// 用户状态定义
+const (
+	UserStatusNo        = 0 //禁用
+	UserStatusPotential = 1 //潜在用户
+	UserStatusFormal    = 2 //正式用户
+)
+
+// 免验证接口
+var NoAuthApiMap = map[string]bool{
+	"/role/menu/buttons":      true,
+	"/role/menu/list":         true,
+	"/department/list":        true,
+	"/sys_user/reset_my_pass": true,
+	"/chart_permission/list":  true,
+	"/user/change_list":       true,
+	"/classify/list":          true,
+	"/seller/list":            true,
+}
+
+var APPNAME string = "东吴CRM"

+ 37 - 0
utils/des3.go

@@ -0,0 +1,37 @@
+package utils
+
+import (
+	"bytes"
+	"crypto/cipher"
+	"crypto/des"
+	"encoding/base64"
+)
+
+// des3 + base64 encrypt
+func DesBase64Encrypt(origData []byte, desKey string) []byte {
+	result, err := TripleDesEncrypt(origData, []byte(desKey))
+	if err != nil {
+		panic(any(err))
+	}
+	return []byte(base64.StdEncoding.EncodeToString(result))
+}
+
+// 3DES加密
+func TripleDesEncrypt(origData, key []byte) ([]byte, error) {
+	block, err := des.NewTripleDESCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	origData = PKCS5Padding(origData, block.BlockSize())
+	// origData = ZeroPadding(origData, block.BlockSize())
+	blockMode := cipher.NewCBCEncrypter(block, key[:8])
+	crypted := make([]byte, len(origData))
+	blockMode.CryptBlocks(crypted, origData)
+	return crypted, nil
+}
+
+func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
+	padding := blockSize - len(ciphertext)%blockSize
+	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+	return append(ciphertext, padtext...)
+}

+ 44 - 0
utils/jwt.go

@@ -0,0 +1,44 @@
+package utils
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/beego/beego/v2/core/logs"
+	"github.com/dgrijalva/jwt-go"
+)
+
+var (
+	KEY = []byte("5McoGDMb5y")
+)
+
+func GenToken(account string) string {
+	token := jwt.New(jwt.SigningMethodHS256)
+	token.Claims = &jwt.StandardClaims{
+		NotBefore: int64(time.Now().Unix()) - int64(1*time.Second),
+		ExpiresAt: int64(time.Now().Unix() + 60*24*60*60),
+		Issuer:    "eta_mini_crm",
+		Subject:   account,
+	}
+	ss, err := token.SignedString(KEY)
+	if err != nil {
+		logs.Error(err)
+		return ""
+	}
+	return ss
+}
+
+// 校验token
+func CheckToken(account, token string) bool {
+	t, err := jwt.Parse(token, func(*jwt.Token) (interface{}, error) {
+		return KEY, nil
+	})
+	if err != nil {
+		fmt.Println(err.Error())
+		return false
+	}
+	if account != t.Claims.(jwt.MapClaims)["sub"] {
+		return false
+	}
+	return t.Valid
+}

+ 90 - 0
utils/logs.go

@@ -0,0 +1,90 @@
+package utils
+
+import (
+	"encoding/json"
+	"os"
+	"path"
+
+	"github.com/beego/beego/v2/core/logs"
+)
+
+const (
+	DefaultBinlogPath = "./etalogs/binlog"
+	DefaultApiLogPath = "./etalogs/apilog"
+)
+
+var (
+	BinLog *logs.BeeLogger
+	ApiLog *logs.BeeLogger
+)
+
+func init() {
+	initApiLog()
+	initBinLog()
+}
+
+// 初始化apilog日志
+func initApiLog() {
+	apilogPath := ApiLogPath
+	if apilogPath == "" {
+		apilogPath = DefaultApiLogPath
+	}
+	apilogFile := ApiLogFile
+	if apilogFile == "" {
+		apilogFile = "apilog.log"
+	}
+	os.MkdirAll(apilogPath, os.ModePerm)
+	logFileName := path.Join(apilogPath, apilogFile)
+	ApiLog = logs.NewLogger(1000000)
+	logConf := getDefaultLogConfig()
+	logConf.FileName = logFileName
+
+	b, _ := json.Marshal(logConf)
+	ApiLog.SetLogger(logs.AdapterFile, string(b))
+	ApiLog.EnableFuncCallDepth(true)
+}
+
+// 初始化binlog日志
+func initBinLog() {
+	binlogPath := BinLogPath
+	if binlogPath == "" {
+		binlogPath = DefaultBinlogPath
+	}
+	binlogFile := BinLogFile
+	if binlogFile == "" {
+		binlogFile = "binlog.log"
+	}
+	os.MkdirAll(binlogPath, os.ModePerm)
+	logFileName := path.Join(binlogPath, binlogFile)
+	BinLog = logs.NewLogger(1000000)
+	logConf := getDefaultLogConfig()
+	logConf.FileName = logFileName
+
+	b, _ := json.Marshal(logConf)
+	BinLog.SetLogger(logs.AdapterFile, string(b))
+	BinLog.EnableFuncCallDepth(true)
+}
+
+type logConfig struct {
+	FileName string `json:"filename" description:"保存的文件名"`
+	MaxLines int    `json:"maxlines"  description:"每个文件保存的最大行数,默认值 1000000"`
+	MaxSize  int    `json:"maxsize" description:"每个文件保存的最大尺寸,默认值是 1 << 28, //256 MB"`
+	Daily    bool   `json:"daily" description:"是否按照每天 logrotate,默认是 true"`
+	MaxDays  int    `json:"maxdays" description:"文件最多保存多少天,默认保存 7 天"`
+	Rotate   bool   `json:"rotate" description:"是否开启 logrotate,默认是 true"`
+	Level    int    `json:"level" description:"日志保存的时候的级别,默认是 Trace 级别"`
+	Color    bool   `json:"color" description:"日志是否输出颜色"`
+}
+
+func getDefaultLogConfig() logConfig {
+	return logConfig{
+		FileName: "",
+		MaxLines: 10000000,
+		MaxSize:  1 << 28,
+		Daily:    true,
+		MaxDays:  LogMaxDays,
+		Rotate:   true,
+		Level:    logs.LevelTrace,
+		//Perm:     "",
+	}
+}

+ 33 - 0
utils/redis.go

@@ -0,0 +1,33 @@
+package utils
+
+import (
+	"eta/eta_mini_crm/utils/redis"
+	"time"
+)
+
+type RedisClient interface {
+	Get(key string) interface{}
+	RedisBytes(key string) (data []byte, err error)
+	RedisString(key string) (data string, err error)
+	RedisInt(key string) (data int, err error)
+	Put(key string, val interface{}, timeout time.Duration) error
+	SetNX(key string, val interface{}, timeout time.Duration) bool
+	Delete(key string) error
+	IsExist(key string) bool
+	LPush(key string, val interface{}) error
+	Brpop(key string, callback func([]byte))
+	GetRedisTTL(key string) time.Duration
+	Incrby(key string, num int) (interface{}, error)
+	Do(commandName string, args ...interface{}) (reply interface{}, err error)
+}
+
+func initRedis(redisType string, conf string) (redisClient RedisClient, err error) {
+	switch redisType {
+	case "cluster": // 集群
+		redisClient, err = redis.InitClusterRedis(conf)
+	default: // 默认走单机
+		redisClient, err = redis.InitStandaloneRedis(conf)
+	}
+
+	return
+}

+ 263 - 0
utils/redis/cluster_redis.go

@@ -0,0 +1,263 @@
+package redis
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/go-redis/redis/v8"
+)
+
+// ClusterRedisClient
+// @Description: 集群的redis客户端
+type ClusterRedisClient struct {
+	redisClient *redis.ClusterClient
+}
+
+var DefaultKey = "zcmRedis"
+
+// InitClusterRedis
+// @Description: 初始化集群redis客户端
+// @param config
+// @return clusterRedisClient
+// @return err
+func InitClusterRedis(config string) (clusterRedisClient *ClusterRedisClient, err error) {
+	var cf map[string]string
+	err = json.Unmarshal([]byte(config), &cf)
+	if err != nil {
+		return
+	}
+
+	// 集群地址
+	connList := make([]string, 0)
+	if _, ok := cf["conn"]; !ok {
+		err = errors.New("config has no conn key")
+		return
+	} else {
+		connList = strings.Split(cf["conn"], ",")
+		if len(connList) <= 1 {
+			err = errors.New("conn address less than or equal to 1")
+			return
+		}
+	}
+
+	// 密码
+	if _, ok := cf["password"]; !ok {
+		cf["password"] = ""
+	}
+
+	// 创建 Redis 客户端配置对象
+	clusterOptions := &redis.ClusterOptions{
+		Addrs:    connList, // 设置 Redis 节点的 IP 地址和端口号
+		Password: cf["password"],
+	}
+
+	// 创建 Redis 集群客户端
+	client := redis.NewClusterClient(clusterOptions)
+
+	// 测试连接并获取信息
+	_, err = client.Ping(context.TODO()).Result()
+	if err != nil {
+		err = errors.New("redis 链接失败:" + err.Error())
+		return
+	}
+
+	clusterRedisClient = &ClusterRedisClient{redisClient: client}
+
+	return
+}
+
+// Get
+// @Description: 根据key获取数据(其实是返回的字节编码)
+// @receiver rc
+// @param key
+// @return interface{}
+func (rc *ClusterRedisClient) Get(key string) interface{} {
+	data, err := rc.redisClient.Get(context.TODO(), key).Bytes()
+	if err != nil {
+		return nil
+	}
+
+	return data
+}
+
+// RedisBytes
+// @Description: 根据key获取字节编码数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *ClusterRedisClient) RedisBytes(key string) (data []byte, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Bytes()
+
+	return
+}
+
+// RedisString
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *ClusterRedisClient) RedisString(key string) (data string, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Result()
+
+	return
+}
+
+// RedisInt
+// @Description: 根据key获取int数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *ClusterRedisClient) RedisInt(key string) (data int, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Int()
+
+	return
+}
+
+// Put
+// @Description: put一个数据到redis
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return error
+func (rc *ClusterRedisClient) Put(key string, val interface{}, timeout time.Duration) error {
+	var err error
+	err = rc.redisClient.SetEX(context.TODO(), key, val, timeout).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HSet(context.TODO(), DefaultKey, key, true).Err()
+
+	return err
+}
+
+// SetNX
+// @Description: 设置一个会过期时间的值
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return bool
+func (rc *ClusterRedisClient) SetNX(key string, val interface{}, timeout time.Duration) bool {
+	result, err := rc.redisClient.SetNX(context.TODO(), key, val, timeout).Result()
+	if err != nil {
+		return false
+	}
+
+	return result
+}
+
+// Delete
+// @Description: 删除redis中的键值对
+// @receiver rc
+// @param key
+// @return error
+func (rc *ClusterRedisClient) Delete(key string) error {
+	var err error
+
+	err = rc.redisClient.Del(context.TODO(), key).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+
+	return err
+}
+
+// IsExist
+// @Description: 根据key判断是否写入缓存中
+// @receiver rc
+// @param key
+// @return bool
+func (rc *ClusterRedisClient) IsExist(key string) bool {
+	result, err := rc.redisClient.Exists(context.TODO(), key).Result()
+	if err != nil {
+		return false
+	}
+	if result == 0 {
+		_ = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+		return false
+	}
+
+	return true
+}
+
+// LPush
+// @Description: 写入list
+// @receiver rc
+// @param key
+// @param val
+// @return error
+func (rc *ClusterRedisClient) LPush(key string, val interface{}) error {
+	data, _ := json.Marshal(val)
+	err := rc.redisClient.LPush(context.TODO(), key, data).Err()
+
+	return err
+}
+
+// Brpop
+// @Description: 从list中读取
+// @receiver rc
+// @param key
+// @param callback
+func (rc *ClusterRedisClient) Brpop(key string, callback func([]byte)) {
+	values, err := rc.redisClient.BRPop(context.TODO(), 1*time.Second, key).Result()
+	if err != nil {
+		return
+	}
+	if len(values) < 2 {
+		fmt.Println("assert is wrong")
+		return
+	}
+
+	callback([]byte(values[1]))
+
+}
+
+// GetRedisTTL
+// @Description: 获取key的过期时间
+// @receiver rc
+// @param key
+// @return time.Duration
+func (rc *ClusterRedisClient) GetRedisTTL(key string) time.Duration {
+	value, err := rc.redisClient.TTL(context.TODO(), key).Result()
+	if err != nil {
+		return 0
+	}
+
+	return value
+
+}
+
+// Incrby
+// @Description: 设置自增值
+// @receiver rc
+// @param key
+// @param num
+// @return interface{}
+// @return error
+func (rc *ClusterRedisClient) Incrby(key string, num int) (interface{}, error) {
+	return rc.redisClient.IncrBy(context.TODO(), key, int64(num)).Result()
+}
+
+// Do
+// @Description: cmd执行redis命令
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *ClusterRedisClient) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
+	newArgs := []interface{}{commandName}
+	newArgs = append(newArgs, args...)
+	return rc.redisClient.Do(context.TODO(), newArgs...).Result()
+}

+ 257 - 0
utils/redis/standalone_redis.go

@@ -0,0 +1,257 @@
+package redis
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/go-redis/redis/v8"
+	"strconv"
+	"time"
+)
+
+// StandaloneRedisClient
+// @Description: 单机redis客户端
+type StandaloneRedisClient struct {
+	redisClient *redis.Client
+}
+
+func InitStandaloneRedis(config string) (standaloneRedis *StandaloneRedisClient, err error) {
+	var cf map[string]string
+	err = json.Unmarshal([]byte(config), &cf)
+	if err != nil {
+		return
+	}
+	//if _, ok := cf["key"]; !ok {
+	//	cf["key"] = DefaultKey
+	//}
+
+	if _, ok := cf["conn"]; !ok {
+		err = errors.New("config has no conn key")
+		return
+	}
+
+	// db库
+	dbNum := 0
+	// 如果指定了db库
+	if _, ok := cf["dbNum"]; ok {
+		dbNum, err = strconv.Atoi(cf["dbNum"])
+		if err != nil {
+			return
+		}
+	}
+
+	// 密码
+	if _, ok := cf["password"]; !ok {
+		cf["password"] = ""
+	}
+
+	client := redis.NewClient(&redis.Options{
+		Addr:     cf["conn"],
+		Password: cf["password"],
+		DB:       dbNum,
+		//PoolSize: 10, //连接池最大socket连接数,默认为10倍CPU数, 10 * runtime.NumCPU(暂不配置)
+	})
+
+	_, err = client.Ping(context.TODO()).Result()
+	if err != nil {
+		err = errors.New("redis 链接失败:" + err.Error())
+		return
+	}
+
+	standaloneRedis = &StandaloneRedisClient{redisClient: client}
+
+	return
+}
+
+// Get
+// @Description: 根据key获取数据(其实是返回的字节编码)
+// @receiver rc
+// @param key
+// @return interface{}
+func (rc *StandaloneRedisClient) Get(key string) interface{} {
+	data, err := rc.redisClient.Get(context.TODO(), key).Bytes()
+	if err != nil {
+		return nil
+	}
+
+	return data
+}
+
+// RedisBytes
+// @Description: 根据key获取字节编码数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *StandaloneRedisClient) RedisBytes(key string) (data []byte, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Bytes()
+
+	return
+}
+
+// RedisString
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *StandaloneRedisClient) RedisString(key string) (data string, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Result()
+
+	return
+}
+
+// RedisInt
+// @Description: 根据key获取int数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *StandaloneRedisClient) RedisInt(key string) (data int, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Int()
+
+	return
+}
+
+// Put
+// @Description: put一个数据到redis
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return error
+func (rc *StandaloneRedisClient) Put(key string, val interface{}, timeout time.Duration) error {
+	var err error
+	err = rc.redisClient.SetEX(context.TODO(), key, val, timeout).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HSet(context.TODO(), DefaultKey, key, true).Err()
+
+	return err
+}
+
+// SetNX
+// @Description: 设置一个会过期时间的值
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return bool
+func (rc *StandaloneRedisClient) SetNX(key string, val interface{}, timeout time.Duration) bool {
+	result, err := rc.redisClient.SetNX(context.TODO(), key, val, timeout).Result()
+	if err != nil {
+		return false
+	}
+
+	return result
+}
+
+// Delete
+// @Description: 删除redis中的键值对
+// @receiver rc
+// @param key
+// @return error
+func (rc *StandaloneRedisClient) Delete(key string) error {
+	var err error
+
+	err = rc.redisClient.Del(context.TODO(), key).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+
+	return err
+}
+
+// IsExist
+// @Description: 根据key判断是否写入缓存中
+// @receiver rc
+// @param key
+// @return bool
+func (rc *StandaloneRedisClient) IsExist(key string) bool {
+	result, err := rc.redisClient.Exists(context.TODO(), key).Result()
+	if err != nil {
+		return false
+	}
+	if result == 0 {
+		_ = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+		return false
+	}
+
+	return true
+}
+
+// LPush
+// @Description: 写入list
+// @receiver rc
+// @param key
+// @param val
+// @return error
+func (rc *StandaloneRedisClient) LPush(key string, val interface{}) error {
+	data, _ := json.Marshal(val)
+	err := rc.redisClient.LPush(context.TODO(), key, data).Err()
+
+	return err
+}
+
+// Brpop
+// @Description: 从list中读取
+// @receiver rc
+// @param key
+// @param callback
+func (rc *StandaloneRedisClient) Brpop(key string, callback func([]byte)) {
+	values, err := rc.redisClient.BRPop(context.TODO(), 1*time.Second, key).Result()
+	if err != nil {
+		return
+	}
+	if len(values) < 2 {
+		fmt.Println("assert is wrong")
+		return
+	}
+
+	callback([]byte(values[1]))
+
+}
+
+// GetRedisTTL
+// @Description: 获取key的过期时间
+// @receiver rc
+// @param key
+// @return time.Duration
+func (rc *StandaloneRedisClient) GetRedisTTL(key string) time.Duration {
+	value, err := rc.redisClient.TTL(context.TODO(), key).Result()
+	if err != nil {
+		return 0
+	}
+
+	return value
+
+}
+
+// Incrby
+// @Description: 设置自增值
+// @receiver rc
+// @param key
+// @param num
+// @return interface{}
+// @return error
+func (rc *StandaloneRedisClient) Incrby(key string, num int) (interface{}, error) {
+	return rc.redisClient.IncrBy(context.TODO(), key, int64(num)).Result()
+}
+
+// Do
+// @Description: cmd执行redis命令
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *StandaloneRedisClient) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
+	newArgs := []interface{}{commandName}
+	newArgs = append(newArgs, args...)
+	return rc.redisClient.Do(context.TODO(), newArgs...).Result()
+}