소스 검색

init project

hsun 2 년 전
커밋
aa5320be2e
58개의 변경된 파일6981개의 추가작업 그리고 0개의 파일을 삭제
  1. 10 0
      .gitignore
  2. 80 0
      config/config.go
  3. 34 0
      controller/auth.go
  4. 19 0
      controller/resource/video.go
  5. 155 0
      controller/resp/base.go
  6. 422 0
      controller/system/sys_admin.go
  7. 202 0
      controller/system/sys_dept.go
  8. 296 0
      controller/system/sys_menu.go
  9. 149 0
      controller/system/sys_role.go
  10. 1 0
      core/config.go
  11. 136 0
      core/log.go
  12. 32 0
      core/run_server.go
  13. 89 0
      docs/docs.go
  14. 21 0
      docs/swagger.json
  15. 16 0
      docs/swagger.yaml
  16. 60 0
      global/global.go
  17. 46 0
      global/validator.go
  18. 32 0
      go.mod
  19. 73 0
      init_serve/mysql.go
  20. 25 0
      init_serve/redis.go
  21. 36 0
      init_serve/router.go
  22. 5 0
      init_serve/task.go
  23. 0 0
      logic/logic.txt
  24. 23 0
      main.go
  25. 27 0
      middleware/common.go
  26. 27 0
      middleware/cors.go
  27. 57 0
      middleware/recover.go
  28. 81 0
      middleware/token.go
  29. 80 0
      middleware/token_no_login.go
  30. 171 0
      models/base/base.go
  31. 178 0
      models/base/page.go
  32. 13 0
      models/base/time_base.go
  33. 197 0
      models/system/sys_admin.go
  34. 139 0
      models/system/sys_dept.go
  35. 136 0
      models/system/sys_menu.go
  36. 102 0
      models/system/sys_role.go
  37. 42 0
      models/system/sys_role_menu.go
  38. 38 0
      models/system/sys_session.go
  39. 13 0
      routers/auth.go
  40. 12 0
      routers/resource.go
  41. 49 0
      routers/system.go
  42. 30 0
      services/alarm_msg/alarm_msg.go
  43. 446 0
      services/resource/email.go
  44. 264 0
      services/resource/oss.go
  45. 69 0
      services/resource/pdf.go
  46. 87 0
      services/system/sys_admin.go
  47. 282 0
      services/system/sys_dept.go
  48. 254 0
      services/system/sys_menu.go
  49. 190 0
      services/system/sys_role.go
  50. 0 0
      static/static.txt
  51. 72 0
      task/task.go
  52. 1029 0
      utils/common.go
  53. 36 0
      utils/constants.go
  54. 191 0
      utils/des3.go
  55. 48 0
      utils/directory.go
  56. 357 0
      utils/drawtext.go
  57. 214 0
      utils/validator.go
  58. 88 0
      utils/whereBuild.go

+ 10 - 0
.gitignore

@@ -0,0 +1,10 @@
+/.idea/
+/log
+latest_log
+/config/config.yaml
+.DS_Store
+go.sum
+/binlog
+latest_binlog
+/rdlucklog
+/*.exe

+ 80 - 0
config/config.go

@@ -0,0 +1,80 @@
+package config
+
+import "time"
+
+type Config struct {
+	Log      Log      `mapstructure:"log" json:"log" yaml:"log"`
+	Serve    Serve    `mapstructure:"serve" json:"serve" yaml:"serve"`
+	Mysql    Mysql    `mapstructure:"mysql" json:"mysql" yaml:"mysql"`
+	Redis    Redis    `mapstructure:"redis" json:"redis" yaml:"redis"`
+	AliOss   AliOss   `mapstructure:"ali-oss" json:"ali-oss" yaml:"ali-oss"`
+	EsClient EsClient `mapstructure:"es_client" json:"es_client" yaml:"es_client"`
+}
+
+// Serve gin服务配置
+type Serve struct {
+	Port            int    `mapstructure:"port" json:"port" yaml:"port" description:"gin开启监听http服务的端口"`
+	RunMode         string `mapstructure:"run-mode" json:"run-mode" yaml:"run-mode" description:"gin运行模式的默认,枚举值:debug,release"`
+	UseRedis        bool   `mapstructure:"use-redis" json:"use-redis" yaml:"use-redis" description:"是否使用redis"`
+	AppName         string `mapstructure:"app-name" json:"app-name" yaml:"app-name" description:"项目名称"`
+	StaticDir       string `mapstructure:"static-dir" json:"static-dir" yaml:"static-dir" description:"上传的文件存储目录地址"`
+	LibreOfficePath string `mapstructure:"libreOfficePath" json:"libreOfficePath" yaml:"libreOfficePath" description:"办公软件相关组件"`
+}
+
+// Log 日志配置
+type Log struct {
+	Prefix         string `mapstructure:"prefix" json:"prefix" yaml:"prefix" description:"日志输出前缀"`
+	LogFile        bool   `mapstructure:"log-file" json:"logFile" yaml:"log-file" description:""`
+	Stdout         string `mapstructure:"stdout" json:"stdout" yaml:"stdout" description:""`
+	FileStdout     string `mapstructure:"file-stdout" json:"file-stdout" yaml:"file-stdout" description:""`
+	SaveMaxDay     int    `mapstructure:"save-max-day" json:"save-max-day" yaml:"save-max-day" description:"最多保留多少天的日志"`
+	CuttingDay     int    `mapstructure:"cutting-day" json:"cutting-day" yaml:"cutting-day" description:"相隔几天切割文件"`
+	LogDirPath     string `mapstructure:"log-dir-path" json:"log-dir-path" yaml:"log-dir-path" description:"日志目录"`
+	LogSoftLink    string `mapstructure:"log-soft-link" json:"log-soft-link" yaml:"log-soft-link" description:"日志软链接"`
+	BinlogDirPath  string `mapstructure:"binlog-dir-path" json:"binlog-dir-path" yaml:"binlog-dir-path" description:"binlog日志目录"`
+	BinlogSoftLink string `mapstructure:"binlog-soft-link" json:"binlog-soft-link" yaml:"binlog-soft-link" description:"binlog日志软链接"`
+}
+
+// Mysql 数据库配置
+type Mysql struct {
+	//LogMode             bool        `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode" description:"是否开启日志"`
+	Stdout              bool        `mapstructure:"stdout" json:"stdout" yaml:"stdout" description:"日志是否输出在控制台"`
+	DefaultDsnAliasName string      `mapstructure:"default-dsn-alias-name" json:"default-dsn-alias-name" yaml:"default-dsn-alias-name" description:"默认的数据库连接别名"`
+	List                []MysqlConn `mapstructure:"list" json:"list" yaml:"list" description:"数据库链接配置列表"`
+}
+
+// MysqlConn mysql数据链接配置
+type MysqlConn struct {
+	Dsn          string `mapstructure:"dsn" json:"dsc" yaml:"dsn" description:"数据库连接配置"`
+	AliasName    string `mapstructure:"alias-name" json:"alias-name" yaml:"alias-name" description:"数据库别名"`
+	MaxIdleConns int    `mapstructure:"max-idle-conns" json:"max-idle-conns" yaml:"max-idle-conns" description:"最大空闲连接"`
+	MaxOpenConns int    `mapstructure:"max-open-conns" json:"max-open-conns" yaml:"max-open-conns" description:"最大连接数"`
+}
+
+// Redis redis配置
+type Redis struct {
+	Address  string `mapstructure:"address" json:"address" yaml:"address" description:"redis服务链接地址"`
+	Password string `mapstructure:"password" json:"password" yaml:"password" description:"redis服务密码"`
+	Db       int    `mapstructure:"db" json:"db" yaml:"db" description:"默认使用的redis库"`
+}
+
+// AliOss aliOss配置
+type AliOss struct {
+	BucketName      string `mapstructure:"bucket-name" json:"bucket-name" yaml:"bucket-name"`
+	EndPoint        string `mapstructure:"end-point" json:"end-point" yaml:"end-point"`
+	ImgHost         string `mapstructure:"img-host" json:"img-host" yaml:"img-host" description:"阿里oss主机地址"`
+	UploadDir       string `mapstructure:"upload-dir" json:"upload-dir" yaml:"upload-dir" description:"图片上传的文件目录"`
+	UploadAudioDir  string `mapstructure:"upload-audio-dir" json:"upload-audio-dir" yaml:"upload-audio-dir" description:"视频上传的文件目录"`
+	AccessKeyId     string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id" description:"access-key-id"`
+	AccessKeySecret string `mapstructure:"access-key-secret" json:"access-key-secret" yaml:"access-key-secret" description:"access-key-secret"`
+}
+
+// EsClient es客户端配置
+type EsClient struct {
+	Endpoints string        `json:"endpoints" yaml:"endpoints" description:"终端地址"`
+	Username  string        `json:"username" yaml:"username" description:"用户"`
+	Password  string        `json:"password" yaml:"password" description:"密码"`
+	Timeout   time.Duration `json:"timeout" yaml:"timeout" description:"超时时间,单位ms"`
+	Log       string        `json:"log" yaml:"log" description:"日志目录"`
+	Prefix    string        `json:"prefix" yaml:"prefix" description:"索引前缀"`
+}

+ 34 - 0
controller/auth.go

@@ -0,0 +1,34 @@
+package controller
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/go-playground/validator/v10"
+	"hongze/hrms_api/controller/resp"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/system"
+	systemService "hongze/hrms_api/services/system"
+)
+
+type AuthController struct {
+}
+
+func (a *AuthController) Login(c *gin.Context) {
+	req := new(system.LoginReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	ret, err, errMsg := systemService.Login(req.AdminName, req.Password, req.IsRemember)
+	if err != nil {
+		resp.FailMsg(errMsg, err.Error(), c)
+		return
+	}
+	resp.OkData("登录成功", ret, c)
+	return
+}

+ 19 - 0
controller/resource/video.go

@@ -0,0 +1,19 @@
+package resource
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hrms_api/controller/resp"
+	services "hongze/hrms_api/services/resource"
+)
+
+type VideoController struct{}
+
+func (v *VideoController) GetOssStsToken(c *gin.Context) {
+	ret, err := services.GetOssSTSToken()
+	if err != nil {
+		resp.FailMsg("获取STSToken失败", "获取STSToken失败"+err.Error(), c)
+		return
+	}
+	resp.OkData("获取成功", ret, c)
+	return
+}

+ 155 - 0
controller/resp/base.go

@@ -0,0 +1,155 @@
+package resp
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"hongze/hrms_api/global"
+	"strings"
+)
+
+var (
+	OK_CODE             = 200  //业务成功
+	FAIL_CODE           = 400  //业务错误
+	TOKEN_ERROR_CODE    = 401  //toke异常
+	NO_AUTH             = 403  //没有权限
+	SPECIFIC_FAIL_CODE  = 4001 // 业务指定错误
+	HASFORBIDDEN_CODE   = 4010 // 管理员账号被禁用
+	PASSWORDCHANGE_CODE = 4011 // 管理员账号被禁用
+)
+
+type ResultData struct {
+	Code   int         `json:"code" description:"状态码"`
+	Msg    string      `json:"msg" description:"提示信息"`
+	Data   interface{} `json:"data" description:"返回数据"`
+	ErrMsg string      `json:"-" description:"错误信息,不用返回给前端,只是做日志记录"`
+}
+
+func result(code int, resultData ResultData, c *gin.Context) {
+	jsonByte, _ := json.Marshal(resultData)
+	token := c.Request.Header.Get("Authorization")
+	if token == "" {
+		token = c.DefaultQuery("authorization", "")
+		if token == "" {
+			token = c.DefaultQuery("Authorization", "")
+		}
+	}
+	logSlice := make([]string, 0)
+	logSlice = append(logSlice, fmt.Sprint("Url:", c.Request.RequestURI))
+	logSlice = append(logSlice, fmt.Sprint("Token:", token))
+	logSlice = append(logSlice, fmt.Sprint("resultData:", string(jsonByte)))
+
+	//记录错误日志
+	if resultData.ErrMsg != "" {
+		logSlice = append(logSlice, fmt.Sprint("ErrMsg:", resultData.ErrMsg))
+		global.LOG.Info(strings.Join(logSlice, ";"))
+	}
+
+	// 测试环境不加密
+	//if global.CONFIG.Serve.RunMode == "debug" {
+	c.JSON(code, resultData)
+	/*} else {
+		global.LOG.Info(strings.Join(logSlice, ";"))
+		encryptResult := utils.DesBase64Encrypt(jsonByte)
+		c.JSON(code, string(encryptResult))
+	}*/
+	c.Abort()
+}
+
+// OK 操作成功
+func Ok(msg string, c *gin.Context) {
+	resultData := ResultData{
+		Code: OK_CODE,
+		Msg:  msg,
+	}
+	result(200, resultData, c)
+}
+
+// OkData 成功返回数据
+func OkData(msg string, data interface{}, c *gin.Context) {
+	resultData := ResultData{
+		Code: OK_CODE,
+		Msg:  msg,
+		Data: data,
+	}
+	result(200, resultData, c)
+}
+
+// Fail 操作失败
+func Fail(msg string, c *gin.Context) {
+	resultData := ResultData{
+		Code: FAIL_CODE,
+		Msg:  msg,
+	}
+	result(200, resultData, c)
+}
+
+// FailData 成功返回数据
+func FailData(msg string, data interface{}, c *gin.Context) {
+	resultData := ResultData{
+		Code: FAIL_CODE,
+		Msg:  msg,
+		Data: data,
+	}
+	result(200, resultData, c)
+}
+
+// Custom 自定义状态码+操作成功
+func Custom(code int, msg string, c *gin.Context) {
+	resultData := ResultData{
+		Code: code,
+		Msg:  msg,
+	}
+	result(200, resultData, c)
+}
+
+// CustomData 自定义状态码+返回数据
+func CustomData(code int, msg string, data interface{}, c *gin.Context) {
+	resultData := ResultData{
+		Code: code,
+		Msg:  msg,
+		Data: data,
+	}
+	result(200, resultData, c)
+}
+
+// TokenError token异常
+func TokenError(data interface{}, message, errMsg string, c *gin.Context) {
+	resultData := ResultData{
+		Code:   TOKEN_ERROR_CODE,
+		Msg:    message,
+		Data:   data,
+		ErrMsg: errMsg,
+	}
+	result(200, resultData, c)
+}
+
+// AuthError 没有权限
+func AuthError(data interface{}, message string, c *gin.Context) {
+	resultData := ResultData{
+		Code: NO_AUTH,
+		Msg:  message,
+		Data: data,
+	}
+	result(200, resultData, c)
+}
+
+// SpecificFail 业务指定错误
+func SpecificFail(code int, data interface{}, message string, c *gin.Context) {
+	resultData := ResultData{
+		Code: code,
+		Msg:  message,
+		Data: data,
+	}
+	result(200, resultData, c)
+}
+
+// FailMsg 操作失败
+func FailMsg(msg, errMsg string, c *gin.Context) {
+	resultData := ResultData{
+		Code:   FAIL_CODE,
+		Msg:    msg,
+		ErrMsg: errMsg,
+	}
+	result(200, resultData, c)
+}

+ 422 - 0
controller/system/sys_admin.go

@@ -0,0 +1,422 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/go-playground/validator/v10"
+	"hongze/hrms_api/controller/resp"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/base"
+	"hongze/hrms_api/models/system"
+	systemService "hongze/hrms_api/services/system"
+	"hongze/hrms_api/utils"
+	"time"
+)
+
+type SysAdminController struct {
+}
+
+func (a *SysAdminController) Add(c *gin.Context) {
+	req := new(system.SysAdminAddReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	// 判断账号名是否已存在
+	item := new(system.SysAdmin)
+	_, err = item.GetAdminByAdminName(req.AdminName)
+	if err != nil && err != utils.ErrNoRow {
+		resp.FailMsg("查询管理员信息失败", "查询管理员信息失败,Err:"+err.Error(), c)
+		return
+	}
+	if err == nil {
+		resp.Fail("用户名重复,请重新设置", c)
+		return
+	}
+	// 判断手机号是否已存在
+	_, err = item.GetAdminByMobile(req.Mobile)
+	if err != nil && err != utils.ErrNoRow {
+		resp.FailMsg("查询管理员信息失败", "查询管理员信息失败,Err:"+err.Error(), c)
+		return
+	}
+	if err == nil {
+		resp.Fail("手机号已被其他账号绑定,请重新设置", c)
+		return
+	}
+
+	//todo 验证部门信息
+	//todo 验证角色信息
+	// 新增账号
+	item = &system.SysAdmin{
+		AdminName: req.AdminName,
+		RealName:  req.RealName,
+		Password:  req.Password,
+		Enabled:   req.Enabled,
+		Email:     req.Email,
+		Mobile:    req.Mobile,
+		DeptId:    req.DeptId,
+		RoleId:    req.RoleId,
+		Position:  req.Position,
+		Remark:    req.Remark,
+	}
+	if req.Enabled == 0 {
+		item.DisableTime = time.Now()
+	}
+	err = item.Add()
+	if err != nil {
+		resp.FailMsg("保存失败", "保存失败,Err:"+err.Error(), c)
+		return
+	}
+	resp.Ok("保存成功", c)
+}
+
+// List 部门列表
+func (a *SysAdminController) List(c *gin.Context) {
+	req := new(system.SysAdminListReq)
+	err := c.BindQuery(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	page := new(base.Page)
+	page.SetPageSize(req.PageSize)
+	page.SetCurrent(req.Current)
+	item := new(system.SysAdmin)
+
+	condition := "1=1"
+	var pars []interface{}
+	var total int64
+	var tmpList []system.SysAdminListTmpItem
+	var list []*system.SysAdminListItem
+
+	if req.DeptId > 0 {
+		deptIdMap, err, errMsg := systemService.GetChildDeptIds(0)
+		if err != nil {
+			resp.FailMsg(errMsg, err.Error(), c)
+			return
+		}
+		if deptIds, ok := deptIdMap[req.DeptId]; ok {
+			condition += " and sys_admin.dept_id IN (?)"
+			pars = append(pars, deptIds)
+		} else {
+			page.SetTotal(total)
+			baseData := new(base.BaseData)
+			baseData.SetPage(page)
+			baseData.SetList(list)
+			resp.OkData("获取成功", baseData, c)
+		}
+	}
+	if req.KeyWord != "" {
+		keyLike := `%` + req.KeyWord + `%`
+		condition += " AND (sys_admin.real_name LIKE ? OR sys_admin.mobile LIKE ? OR sys_admin.admin_name LIKE ?) "
+		pars = append(pars, keyLike, keyLike, keyLike)
+	}
+
+	if req.Enabled != "" {
+		condition += " AND sys_admin.enabled = ? "
+		pars = append(pars, req.Enabled)
+	}
+	page.AddOrderItem(base.OrderItem{Column: "sys_admin.create_time", Asc: false})
+	total, tmpList, err = item.SelectPage(page, condition, pars)
+	if err != nil {
+		resp.FailData("获取失败", "获取失败,Err:"+err.Error(), c)
+		return
+	}
+	if len(tmpList) > 0 {
+		deptNameMap, err, errMsg := systemService.GetChildDeptNames()
+		if err != nil {
+			resp.FailMsg(errMsg, err.Error(), c)
+			return
+		}
+		for _, v := range tmpList {
+			tmp := &system.SysAdminListItem{
+				AdminId:    v.AdminId,
+				AdminName:  v.AdminName,
+				RealName:   v.RealName,
+				Enabled:    v.Enabled,
+				Email:      v.Email,
+				Mobile:     v.Mobile,
+				DeptId:     v.DeptId,
+				RoleId:     v.RoleId,
+				RoleName:   v.RoleName,
+				Position:   v.Position,
+				Remark:     v.Remark,
+				CreateTime: utils.TimeTransferString(utils.FormatDateTime, v.CreateTime),
+				ModifyTime: utils.TimeTransferString(utils.FormatDateTime, v.ModifyTime),
+			}
+
+			if name, ok := deptNameMap[v.DeptId]; ok {
+				tmp.DeptFullName = name
+			}
+			list = append(list, tmp)
+		}
+	}
+	page.SetTotal(total)
+	baseData := new(base.BaseData)
+	baseData.SetPage(page)
+	baseData.SetList(list)
+	resp.OkData("获取成功", baseData, c)
+}
+
+// @Title 修改密码
+// @Description 修改密码
+// @Param	request	body models.ModifyPwdReq true "type json string"
+// @Success 200 {object} models.LoginResp
+// @router /modify/pwd [post]
+func (a *SysAdminController) ModifyPwd(c *gin.Context) {
+	req := new(system.ModifyPwdReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	//获取jwt数据失败
+	var adminInfo *system.SysAdmin
+	// 判断账号名是否已存在
+	admin := new(system.SysAdmin)
+	//查询账号是否存在
+	adminInfo, err = admin.GetAdminByAdminId(req.AdminId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			resp.Fail("账号不存在", c)
+			return
+		}
+		resp.FailMsg("查询管理员信息失败", "查询管理员信息失败,Err:"+err.Error(), c)
+		return
+	}
+	if req.Pwd != req.ConfirmPwd {
+		resp.Fail("新密码两次输入不一致,请核对", c)
+		return
+	}
+	if adminInfo.Password == req.Pwd {
+		resp.Fail("新密码不能与旧密码一致,请重试", c)
+		return
+	}
+	adminInfo.Password = req.Pwd
+	err = adminInfo.Update([]string{"password"})
+	if err != nil {
+		resp.FailData("密码更新失败", "Err:"+err.Error(), c)
+		return
+	}
+
+	resp.Ok("修改成功", c)
+	return
+}
+
+// @Title 修改密码
+// @Description 修改密码
+// @Param	request	body models.ModifyPwdReq true "type json string"
+// @Success 200 {object} models.LoginResp
+// @router /modify/my/pwd [post]
+func (a *SysAdminController) ModifyMyPwd(c *gin.Context) {
+	req := new(system.ModifyMyPwdReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	claims, _ := c.Get("adminInfo")
+	adminInfo := claims.(*system.SysAdmin)
+	if req.NewPwd != req.ConfirmPwd {
+		resp.Fail("新密码两次输入不一致,请核对", c)
+		return
+	}
+	if adminInfo.Password != req.OldPwd {
+		resp.Fail("旧密码错误,请重新输入", c)
+		return
+	}
+	if adminInfo.Password == req.NewPwd {
+		resp.Fail("新密码不能与旧密码一致,请重试", c)
+		return
+	}
+	adminInfo.Password = req.NewPwd
+	err = adminInfo.Update([]string{"password"})
+	if err != nil {
+		resp.FailData("密码更新失败", "Err:"+err.Error(), c)
+		return
+	}
+
+	resp.Ok("修改成功", c)
+	return
+}
+
+// @Title 修改初始密码
+// @Description 修改初始密码
+// @Param	request	body models.ModifyPwdReq true "type json string"
+// @Success 200 {object} models.LoginResp
+// @router /modify/my/init_pwd [post]
+func (a *SysAdminController) ModifyMyInitPwd(c *gin.Context) {
+	req := new(system.ModifyMyInitPwdReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	claims, _ := c.Get("adminInfo")
+	adminInfo := claims.(*system.SysAdmin)
+	if req.NewPwd != req.ConfirmPwd {
+		resp.Fail("新密码两次输入不一致,请核对", c)
+		return
+	}
+	if adminInfo.Password == req.NewPwd {
+		resp.Fail("新密码不能与旧密码一致,请重试", c)
+		return
+	}
+	adminInfo.Password = req.NewPwd
+	err = adminInfo.Update([]string{"password"})
+	if err != nil {
+		resp.FailData("密码更新失败", "Err:"+err.Error(), c)
+		return
+	}
+
+	resp.Ok("修改成功", c)
+	return
+}
+
+// Delete 删除用户
+func (a *SysAdminController) Delete(c *gin.Context) {
+	req := new(system.SysAdminReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+		return
+	}
+	item := new(system.SysAdmin)
+	item.AdminId = req.AdminId
+	err = item.Delete()
+	if err != nil {
+		resp.FailData("删除失败", "删除失败,Err:"+err.Error(), c)
+		return
+	}
+	resp.Ok("删除成功", c)
+}
+
+// Edit 编辑用户
+func (a *SysAdminController) Edit(c *gin.Context) {
+	req := new(system.SysAdminEditReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	// 判断账号名是否已存在
+	admin := new(system.SysAdmin)
+	//查询账号是否存在
+	item, err := admin.GetAdminByAdminId(req.AdminId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			resp.Fail("账号不存在", c)
+			return
+		}
+		resp.FailData("查询管理员信息失败", "查询管理员信息失败,Err:"+err.Error(), c)
+		return
+	}
+	if item.AdminName != req.AdminName {
+		_, err = item.GetAdminByAdminName(req.AdminName)
+		if err != nil && err != utils.ErrNoRow {
+			resp.FailData("查询管理员信息失败", "查询管理员信息失败,Err:"+err.Error(), c)
+			return
+		}
+		if err == nil {
+			resp.Fail("用户名重复,请重新设置", c)
+			return
+		}
+	}
+	if item.Mobile != req.Mobile {
+		// 判断手机号是否已存在
+		_, err = item.GetAdminByMobile(req.Mobile)
+		if err != nil && err != utils.ErrNoRow {
+			resp.FailData("查询管理员信息失败", "查询管理员信息失败,Err:"+err.Error(), c)
+			return
+		}
+		if err == nil {
+			resp.Fail("手机号已被其他账号绑定,请重新设置", c)
+			return
+		}
+	}
+
+	//todo 验证部门信息
+	//todo 验证角色信息
+	// 更新账号
+	updateStr := []string{"admin_name", "real_name", "enabled", "email", "mobile", "dept_id", "role_id", "position", "remark"}
+	item.AdminName = req.AdminName
+	item.RealName = req.RealName
+	item.Enabled = req.Enabled
+	item.Email = req.Email
+	item.Mobile = req.Mobile
+	item.DeptId = req.DeptId
+	item.RoleId = req.RoleId
+	item.Position = req.Position
+	item.Remark = req.Remark
+	if req.Enabled == 0 {
+		item.DisableTime = time.Now()
+	}
+	err = item.Update(updateStr)
+	if err != nil {
+		resp.FailData("保存失败", "保存失败,Err:"+err.Error(), c)
+		return
+	}
+	resp.Ok("修改成功", c)
+	return
+}
+
+// @Title 修改管理员状态
+// @Description 修改管理员状态
+// @Param	request	body models.ModifyPwdReq true "type json string"
+// @Success 200 {object} models.LoginResp
+// @router /modify/enabled [post]
+func (a *SysAdminController) ModifyEnabled(c *gin.Context) {
+	req := new(system.SysAdminModifyEnabledReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+
+	admin := new(system.SysAdmin)
+	admin.AdminId = req.AdminId
+	admin.Enabled = req.Enabled
+	err = admin.Update([]string{"enabled"})
+	if err != nil {
+		resp.FailData("更新失败", "Err:"+err.Error(), c)
+		return
+	}
+
+	resp.Ok("修改成功", c)
+	return
+}

+ 202 - 0
controller/system/sys_dept.go

@@ -0,0 +1,202 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/go-playground/validator/v10"
+	"hongze/hrms_api/controller/resp"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/base"
+	"hongze/hrms_api/models/system"
+	systemService "hongze/hrms_api/services/system"
+	"hongze/hrms_api/utils"
+)
+
+type SysDeptController struct {
+}
+
+// List 部门列表
+func (s *SysDeptController) List(c *gin.Context) {
+	req := new(system.DeptListReq)
+	err := c.BindQuery(&req)
+	if err != nil {
+		resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+		return
+	}
+	page := new(base.Page)
+	page.SetPageSize(req.PageSize)
+	page.SetCurrent(req.Current)
+	count, list, err, _ := systemService.DeptTreeList(page, req)
+	if err != nil {
+		resp.FailMsg("获取失败", "获取失败,Err:"+err.Error(), c)
+		return
+	}
+	page.SetTotal(count)
+	resultPage := &base.BaseData{
+		Page: page,
+		List: list,
+	}
+	resp.OkData("获取成功", resultPage, c)
+}
+
+// Add 新增部门
+func (s *SysDeptController) Add(c *gin.Context) {
+	req := new(system.SysDeptAddReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	item := new(system.SysDept)
+	item.ParentId = req.ParentId
+	item.DeptName = req.DeptName
+	item.Sort = req.Sort
+	if req.ParentId > 0 {
+		parentInfo, tErr := item.GetDeptByDeptId(req.ParentId)
+		if tErr != nil {
+			if tErr == utils.ErrNoRow {
+				resp.Fail("上级部门不存在", c)
+				return
+			}
+			resp.FailMsg("查询上级部门出错", tErr.Error(), c)
+			return
+		}
+		if parentInfo.RootId == 0 {
+			item.RootId = parentInfo.DeptId
+		} else {
+			item.RootId = parentInfo.RootId
+		}
+
+		item.Level = parentInfo.Level + 1
+	}
+	// 判断是否存在相同的部门名称
+	cond := "parent_id = ? and dept_name = ?"
+	pars := make([]interface{}, 0)
+	pars = append(pars, item.ParentId, item.DeptName)
+	exitList, err := item.GetDeptListByCondition(cond, pars, "")
+	if err != nil {
+		resp.FailMsg("查询部门出错", err.Error(), c)
+		return
+	}
+	if len(exitList) > 0 {
+		resp.Fail("该部门名称已存在", c)
+		return
+	}
+	err = item.Add()
+	if err != nil {
+		resp.FailMsg("保存失败", "保存失败,Err:"+err.Error(), c)
+		return
+	}
+	resp.Ok("保存成功", c)
+}
+
+// Edit 编辑部门
+func (s *SysDeptController) Edit(c *gin.Context) {
+	req := new(system.SysDeptEditReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+		return
+	}
+	item := new(system.SysDept)
+	item.DeptId = req.DeptId
+	item.ParentId = req.ParentId
+	item.DeptName = req.DeptName
+	item.Sort = req.Sort
+
+	//查询当前部门是否已存在
+	deptInfo, err := item.GetDeptByDeptId(req.DeptId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			resp.Fail("该部门不存在", c)
+			return
+		}
+		resp.FailMsg("查询部门出错", err.Error(), c)
+		return
+	}
+	updateCols := make([]string, 0)
+	if deptInfo.ParentId > 0 && deptInfo.ParentId != item.ParentId {
+		parentInfo, tErr := item.GetDeptByDeptId(req.ParentId)
+		if tErr != nil {
+			if tErr == utils.ErrNoRow {
+				resp.Fail("上级部门不存在", c)
+				return
+			}
+			resp.FailMsg("查询上级部门出错", tErr.Error(), c)
+			return
+		}
+		if parentInfo.RootId == 0 {
+			item.RootId = parentInfo.DeptId
+		} else {
+			item.RootId = parentInfo.RootId
+		}
+		item.Level = parentInfo.Level + 1
+		updateCols = append(updateCols, "parent_id", "root_id", "level")
+	} else if req.ParentId == 0 && deptInfo.ParentId != item.ParentId {
+		item.RootId = 0
+		item.Level = 0
+		updateCols = append(updateCols, "parent_id", "root_id", "level")
+	}
+	if deptInfo.Sort != item.Sort {
+		updateCols = append(updateCols, "sort")
+	}
+
+	if deptInfo.DeptName != item.DeptName {
+		cond := "parent_id = ? and dept_name = ?"
+		pars := make([]interface{}, 0)
+		pars = append(pars, item.ParentId, item.DeptName)
+		exitList, err := item.GetDeptListByCondition(cond, pars, "")
+		if err != nil {
+			resp.FailMsg("查询部门出错", err.Error(), c)
+			return
+		}
+		if len(exitList) > 0 {
+			resp.Fail("该部门名称已存在", c)
+			return
+		}
+		updateCols = append(updateCols, "dept_name")
+	}
+	if len(updateCols) == 0 {
+		resp.Ok("保存成功", c)
+		return
+	}
+	err = item.Update(updateCols)
+	if err != nil {
+		resp.FailMsg("保存失败", "保存失败,Err:"+err.Error(), c)
+		return
+	}
+	resp.Ok("保存成功", c)
+}
+
+// Delete 删除部门
+func (s *SysDeptController) Delete(c *gin.Context) {
+	req := new(system.DelSysDeptReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+		return
+	}
+	err, errMsg := systemService.DeleteDept(req)
+	if err != nil {
+		resp.FailMsg(errMsg, err.Error(), c)
+		return
+	}
+	resp.Ok("删除成功", c)
+}
+
+// AdminList 部门列表
+func (s *SysDeptController) AdminList(c *gin.Context) {
+	list, err, errMsg := systemService.DeptAdminTreeList()
+	if err != nil {
+		resp.FailMsg(errMsg, err.Error(), c)
+		return
+	}
+	resultPage := &base.BaseData{
+		List: list,
+	}
+	resp.OkData("获取成功", resultPage, c)
+}

+ 296 - 0
controller/system/sys_menu.go

@@ -0,0 +1,296 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/go-playground/validator/v10"
+	"hongze/hrms_api/controller/resp"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/base"
+	"hongze/hrms_api/models/system"
+	systemService "hongze/hrms_api/services/system"
+	"hongze/hrms_api/utils"
+)
+
+type SysMenuController struct {
+}
+
+func (m *SysMenuController) Add(c *gin.Context) {
+	req := new(system.SysMenuAddReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	item := new(system.SysMenu)
+	item.ParentId = req.ParentId
+	item.Name = req.Name
+	item.Sort = req.Sort
+	item.Hidden = req.Hidden
+	item.HiddenLayout = req.HiddenLayout
+	item.Component = req.Component
+	item.IconPath = req.IconPath
+	item.MenuType = req.MenuType
+	item.Path = req.Path
+	item.PathName = req.PathName
+	item.ButtonCode = req.ButtonCode
+	//添加校验,如果是按钮类型,则父级菜单不允许为0
+	if req.MenuType == 1 {
+		if req.ParentId == 0 {
+			resp.Fail("请设置按钮的父级菜单", c)
+			return
+		}
+		if req.ButtonCode == "" {
+			resp.Fail("请设置按钮标识", c)
+			return
+		}
+	}
+	if req.ParentId > 0 {
+		parentInfo, tErr := item.GetMenuByMenuId(req.ParentId)
+		if tErr != nil {
+			if tErr == utils.ErrNoRow {
+				resp.Fail("上级菜单不存在", c)
+				return
+			}
+			resp.FailMsg("查询上级菜单出错", tErr.Error(), c)
+			return
+		}
+		// 按钮只能挂在二级菜单下面
+		/*if req.MenuType == 1 && parentInfo.ParentId == 0{
+			resp.Fail("按钮只允许挂在二级菜单下面", c)
+			return
+		}*/
+		if parentInfo.RootId == 0 {
+			item.RootId = parentInfo.MenuId
+		} else {
+			item.RootId = parentInfo.RootId
+		}
+
+		item.Level = parentInfo.Level + 1
+		if item.Level >= 3 {
+			resp.Fail("不允许设置四级菜单", c)
+			return
+		}
+	}
+	// 判断是否存在相同的菜单名称
+	cond := "parent_id = ? and name = ? and menu_type=?"
+	pars := make([]interface{}, 0)
+	pars = append(pars, item.ParentId, item.Name, item.MenuType)
+	exitList, err := item.GetMenuListByCondition(cond, pars, "")
+	if err != nil {
+		resp.FailMsg("查询菜单出错", err.Error(), c)
+		return
+	}
+	if len(exitList) > 0 {
+		resp.Fail("该菜单名称已存在", c)
+		return
+	}
+	err = item.Add()
+	if err != nil {
+		resp.FailMsg("保存失败", "保存失败,Err:"+err.Error(), c)
+		return
+	}
+	resp.Ok("保存成功", c)
+}
+
+func (m *SysMenuController) List(c *gin.Context) {
+	//获取jwt数据失败
+	claims, _ := c.Get("adminInfo")
+	adminInfo := claims.(*system.SysAdmin)
+	if adminInfo.RoleId <= 0 {
+		resp.Ok("获取成功", c)
+		return
+	}
+	list, err, errMsg := systemService.MenuList(adminInfo)
+	if err != nil {
+		resp.FailMsg(errMsg, err.Error(), c)
+		return
+	}
+	resultPage := &base.BaseData{
+		List: list,
+	}
+	resp.OkData("获取成功", resultPage, c)
+}
+
+func (m *SysMenuController) AllList(c *gin.Context) {
+	req := new(system.MenuListReq)
+	err := c.BindQuery(&req)
+	if err != nil {
+		resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+		return
+	}
+	page := new(base.Page)
+	page.SetPageSize(req.PageSize)
+	page.SetCurrent(req.Current)
+
+	count, list, err, _ := systemService.AllMenuList(page, req)
+	if err != nil {
+		resp.FailMsg("获取失败", "获取失败,Err:"+err.Error(), c)
+		return
+	}
+	page.SetTotal(count)
+	resultPage := &base.BaseData{
+		Page: page,
+		List: list,
+	}
+	resp.OkData("获取成功", resultPage, c)
+}
+
+func (m *SysMenuController) ButtonList(c *gin.Context) {
+	req := new(system.MenuShortListItem)
+	err := c.BindQuery(&req)
+	if err != nil {
+		resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+		return
+	}
+	claims, _ := c.Get("adminInfo")
+	adminInfo := claims.(*system.SysAdmin)
+	if adminInfo.RoleId <= 0 {
+		resp.Ok("获取成功", c)
+		return
+	}
+	list, err, errMsg := systemService.ButtonList(adminInfo, req)
+	if err != nil {
+		resp.FailMsg(errMsg, err.Error(), c)
+		return
+	}
+	buttonList := make([]*system.SysMenuButtonResp, 0)
+	for _, v := range list {
+		tmp := new(system.SysMenuButtonResp)
+		tmp.MenuId = v.MenuId
+		tmp.ParentId = v.ParentId
+		tmp.MenuType = v.MenuType
+		tmp.Name = v.Name
+		tmp.ButtonCode = v.ButtonCode
+		buttonList = append(buttonList, tmp)
+	}
+
+	resultPage := &base.BaseOnlyData{
+		List: buttonList,
+	}
+	resp.OkData("获取成功", resultPage, c)
+}
+
+func (m *SysMenuController) Edit(c *gin.Context) {
+	req := new(system.SysMenuEditReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	menu := new(system.SysMenu)
+	item, err := menu.GetMenuByMenuId(req.MenuId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			resp.Fail("该菜单不存在", c)
+			return
+		}
+		resp.FailMsg("查询菜单出错", err.Error(), c)
+		return
+	}
+	item.ParentId = req.ParentId
+	item.Name = req.Name
+	item.Sort = req.Sort
+	item.Hidden = req.Hidden
+	item.HiddenLayout = req.HiddenLayout
+	item.Component = req.Component
+	item.IconPath = req.IconPath
+	item.MenuType = req.MenuType
+	item.Path = req.Path
+	item.PathName = req.PathName
+	item.ButtonCode = req.ButtonCode
+
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "parent_id", "name", "sort", "component", "icon_path", "menu_type", "path", "path_name", "button_code", "root_id", "level", "hidden", "hidden_layout")
+	//添加校验,如果是按钮类型,则父级菜单不允许为0
+	if req.MenuType == 1 {
+		if req.ParentId == 0 {
+			resp.Fail("请设置按钮的父级菜单", c)
+			return
+		}
+		if req.ButtonCode == "" {
+			resp.Fail("请设置按钮标识", c)
+			return
+		}
+	}
+	if req.ParentId > 0 {
+		parentInfo, tErr := item.GetMenuByMenuId(req.ParentId)
+		if tErr != nil {
+			if tErr == utils.ErrNoRow {
+				resp.Fail("上级菜单不存在", c)
+				return
+			}
+			resp.FailMsg("查询上级菜单出错", tErr.Error(), c)
+			return
+		}
+		// 按钮只能挂在二级菜单下面
+		/*if req.MenuType == 1 && parentInfo.ParentId == 0{
+			resp.Fail("按钮只允许挂在二级菜单下面", c)
+			return
+		}*/
+		if parentInfo.RootId == 0 {
+			item.RootId = parentInfo.MenuId
+		} else {
+			item.RootId = parentInfo.RootId
+		}
+		item.Level = parentInfo.Level + 1
+		if item.Level >= 3 {
+			resp.Fail("不允许设置四级菜单", c)
+			return
+		}
+	} else if req.ParentId == 0 {
+		item.RootId = 0
+		item.Level = 0
+	}
+	// 判断是否存在相同的菜单名称
+	if req.Name != item.Name {
+		cond := "parent_id = ? and name = ? and menu_type=?"
+		pars := make([]interface{}, 0)
+		pars = append(pars, item.ParentId, item.Name, item.MenuType)
+		exitList, err := item.GetMenuListByCondition(cond, pars, "")
+		if err != nil {
+			resp.FailMsg("查询菜单出错", err.Error(), c)
+			return
+		}
+		if len(exitList) > 0 {
+			resp.Fail("该菜单名称已存在", c)
+			return
+		}
+	}
+
+	err = item.Update(updateCols)
+	if err != nil {
+		resp.FailMsg("保存失败", "保存失败,Err:"+err.Error(), c)
+		return
+	}
+	resp.Ok("保存成功", c)
+}
+
+// Delete 删除菜单
+func (m *SysMenuController) Delete(c *gin.Context) {
+	req := new(system.MenuShortListItem)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+		return
+	}
+	if req.MenuId <= 0 {
+		resp.Fail("请选择要删除的菜单", c)
+		return
+	}
+	err, errMsg := systemService.DeleteMenu(req)
+	if err != nil {
+		resp.FailMsg(errMsg, err.Error(), c)
+		return
+	}
+	resp.Ok("删除成功", c)
+}

+ 149 - 0
controller/system/sys_role.go

@@ -0,0 +1,149 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/go-playground/validator/v10"
+	"hongze/hrms_api/controller/resp"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/base"
+	"hongze/hrms_api/models/system"
+	systemService "hongze/hrms_api/services/system"
+	"hongze/hrms_api/utils"
+)
+
+type SysRoleController struct {
+}
+
+// Add 添加角色
+func (s *SysRoleController) Add(c *gin.Context) {
+	req := new(system.SysRoleAddReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	err, errMsg := systemService.AddRole(req)
+	if err != nil {
+		resp.FailMsg(errMsg, err.Error(), c)
+		return
+	}
+	resp.Ok("保存成功", c)
+	return
+}
+
+// 编辑角色菜单权限
+func (s *SysRoleController) EditMenu(c *gin.Context) {
+	req := new(system.SysRoleMenuEditReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	err, errMsg := systemService.EditRoleMenu(req)
+	if err != nil {
+		resp.FailMsg(errMsg, err.Error(), c)
+		return
+	}
+	resp.Ok("保存成功", c)
+	return
+}
+
+func (s *SysRoleController) List(c *gin.Context) {
+	req := new(system.SysRoleListReq)
+	err := c.BindQuery(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	page := new(base.Page)
+	page.SetPageSize(req.PageSize)
+	page.SetCurrent(req.Current)
+	item := new(system.SysRole)
+
+	var condition string
+	var pars []interface{}
+
+	if req.RoleName != "" {
+		roleName := "%" + req.RoleName + "%"
+		condition += " role_name like ?"
+		pars = append(pars, roleName)
+	}
+	page.AddOrderItem(base.OrderItem{Column: "create_time", Asc: false})
+	total, roles, err := item.SelectPage(page, condition, pars)
+	if err != nil {
+		resp.FailMsg("获取失败", "获取失败,Err:"+err.Error(), c)
+		return
+	}
+	list := make([]*system.SysRoleListItem, 0)
+	for _, v := range roles {
+		tmp := new(system.SysRoleListItem)
+		tmp.RoleId = v.RoleId
+		tmp.RoleName = v.RoleName
+		tmp.CreateTime = utils.TimeTransferString(utils.FormatDateTime, v.CreateTime)
+		tmp.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, v.ModifyTime)
+		list = append(list, tmp)
+	}
+	page.SetTotal(total)
+	baseData := new(base.BaseData)
+	baseData.SetPage(page)
+	baseData.SetList(list)
+	resp.OkData("获取成功", baseData, c)
+}
+
+func (s *SysRoleController) MenuList(c *gin.Context) {
+	req := new(system.SysRoleMenuListReq)
+	err := c.BindQuery(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	list, err, errMsg := systemService.RoleMenuList(req)
+	if err != nil {
+		resp.FailMsg(errMsg, err.Error(), c)
+		return
+	}
+
+	resp.OkData("获取成功", list, c)
+}
+
+// Delete 删除角色
+func (s *SysRoleController) Delete(c *gin.Context) {
+	req := new(system.SysRoleDelReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	err, errMsg := systemService.DeleteRole(req)
+	if err != nil {
+		resp.FailMsg(errMsg, errMsg, c)
+		return
+	}
+	resp.Ok("删除成功", c)
+	return
+}

+ 1 - 0
core/config.go

@@ -0,0 +1 @@
+package core

+ 136 - 0
core/log.go

@@ -0,0 +1,136 @@
+package core
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
+	oplogging "github.com/op/go-logging"
+	"hongze/hrms_api/config"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/utils"
+	"io"
+	"os"
+	"strings"
+	"time"
+)
+
+const (
+	Module = "hongze_yb"
+)
+
+var (
+	defaultFormatter = ` %{time:2006/01/02 - 15:04:05.000} %{longfile} %{color:bold}▶ [%{level:.6s}] %{message}%{color:reset}`
+)
+
+// 初始化日志
+func init() {
+	logConfig := global.CONFIG.Log
+	logger := oplogging.MustGetLogger(Module)
+	var backends []oplogging.Backend
+	//注册输出
+	registerStdout(logConfig, &backends)
+
+	//注册框架输出(日志文件)
+	fileWriter := registerFile(logConfig, &backends, logConfig.LogDirPath, logConfig.LogSoftLink)
+	if fileWriter != nil {
+		if global.CONFIG.Serve.RunMode == "debug" {
+			gin.DefaultWriter = io.MultiWriter(fileWriter, os.Stdout)
+		} else {
+			gin.DefaultWriter = io.MultiWriter(fileWriter)
+		}
+	}
+	oplogging.SetBackend(backends...)
+	global.LOG = logger
+
+	//初始化mysql数据库日志
+	initMysqlLog()
+}
+
+// initMysqlLog 初始化mysql数据库日志
+func initMysqlLog() {
+	logConfig := global.CONFIG.Log
+	var backends []oplogging.Backend
+	//注册输出
+	registerStdout(logConfig, &backends)
+
+	//注册框架输出(日志文件)
+	fileWriter := registerFile(logConfig, &backends, logConfig.BinlogDirPath, logConfig.BinlogSoftLink)
+	global.MYSQL_LOG = fileWriter
+}
+
+func registerStdout(c config.Log, backends *[]oplogging.Backend) {
+	if c.Stdout != "" {
+		level, err := oplogging.LogLevel(c.Stdout)
+		if err != nil {
+			fmt.Println(err)
+		}
+		*backends = append(*backends, createBackend(os.Stdout, c, level))
+	}
+}
+
+// registerFile 注册文件日志
+func registerFile(c config.Log, backends *[]oplogging.Backend, logDir, logSoftLink string) io.Writer {
+	if c.FileStdout != "" {
+		if ok, _ := utils.PathExists(logDir); !ok {
+			// directory not exist
+			fmt.Println("create log directory")
+			_ = os.Mkdir(logDir, os.ModePerm)
+		}
+
+		//日志保留时间
+		saveMaxTime := time.Duration(global.CONFIG.Log.SaveMaxDay) * 24 * time.Hour
+		//日志切割时间(每隔多久切割一次)
+		cuttingTime := time.Duration(global.CONFIG.Log.CuttingDay) * 24 * time.Hour
+
+		//使用rotatelogs
+		fileWriter, err := rotatelogs.New(
+			logDir+string(os.PathSeparator)+"%Y-%m-%d-%H-%M.log",
+			// 生成软链,指向最新日志文件
+			rotatelogs.WithLinkName(logSoftLink),
+			// 以下两个配置不能同时设置,一个是保留天数,一个是保留分拣份数
+			rotatelogs.WithMaxAge(saveMaxTime), //日志保留多久,最小分钟为单位
+			//rotatelogs.WithRotationCount(5),        //number 默认7份 大于7份 或到了清理时间 开始清理
+			// time period of log file switching
+			rotatelogs.WithRotationTime(cuttingTime), //rotate 最小为1分钟轮询。默认60s  低于1分钟就按1分钟来
+		)
+		if err != nil {
+			fmt.Println(err)
+		}
+		level, err := oplogging.LogLevel(c.FileStdout)
+		if err != nil {
+			fmt.Println(err)
+		}
+		*backends = append(*backends, createBackend(fileWriter, c, level))
+
+		return fileWriter
+	}
+	return nil
+}
+
+// createBackend 创建一个新的日志记录器
+func createBackend(w io.Writer, c config.Log, level oplogging.Level) oplogging.Backend {
+	backend := oplogging.NewLogBackend(w, c.Prefix, 0)
+	stdoutWriter := false
+	if w == os.Stdout {
+		stdoutWriter = true
+	}
+	format := getLogFormatter(c, stdoutWriter)
+	backendLeveled := oplogging.AddModuleLevel(oplogging.NewBackendFormatter(backend, format))
+	backendLeveled.SetLevel(level, Module)
+	return backendLeveled
+}
+
+func getLogFormatter(c config.Log, stdoutWriter bool) oplogging.Formatter {
+	pattern := defaultFormatter
+	if !stdoutWriter {
+		// Color is only required for console output
+		// Other writers don't need %{color} tag
+		pattern = strings.Replace(pattern, "%{color:bold}", "", -1)
+		pattern = strings.Replace(pattern, "%{color:reset}", "", -1)
+	}
+	if !c.LogFile {
+		// Remove %{logfile} tag
+		pattern = strings.Replace(pattern, "%{longfile}", "", -1)
+	}
+	return oplogging.MustStringFormatter(pattern)
+}

+ 32 - 0
core/run_server.go

@@ -0,0 +1,32 @@
+package core
+
+import (
+	"fmt"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/init_serve"
+)
+
+func RunServe() {
+	//初始化路由
+	r := init_serve.InitRouter()
+	//初始化mysql数据库
+	init_serve.Mysql()
+	//如果配置了redis,那么链接redis
+	if global.CONFIG.Serve.UseRedis {
+		//初始化redis
+		init_serve.Redis()
+	}
+	//初始化验证器
+	if err := global.InitTrans("zh"); err != nil {
+		fmt.Printf("init trans failed, err:%v\n", err)
+		return
+	}
+	//启动任务
+	go init_serve.InitTask()
+	// 3.监听端口,默认在8080
+	// Run("里面不指定端口号默认为8080")
+	err := r.Run(fmt.Sprint("0.0.0.0:", global.CONFIG.Serve.Port)) // 监听并在 0.0.0.0:8080 上启动服务
+	if err != nil {
+		panic(fmt.Errorf("服务启动失败,Err:%s", err))
+	}
+}

+ 89 - 0
docs/docs.go

@@ -0,0 +1,89 @@
+// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
+// This file was generated by swaggo/swag
+package docs
+
+import (
+	"bytes"
+	"encoding/json"
+	"strings"
+	"text/template"
+
+	"github.com/swaggo/swag"
+)
+
+var doc = `{
+    "schemes": {{ marshal .Schemes }},
+    "swagger": "2.0",
+    "info": {
+        "description": "{{escape .Description}}",
+        "title": "{{.Title}}",
+        "termsOfService": "https://www.hzinsights.com/",
+        "contact": {
+            "name": "www.hzinsights.com/",
+            "url": "https://www.hzinsights.com/",
+            "email": "pyan@hzinsights.com"
+        },
+        "license": {
+            "name": "Apache 2.0",
+            "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+        },
+        "version": "{{.Version}}"
+    },
+    "host": "{{.Host}}",
+    "basePath": "{{.BasePath}}",
+    "paths": {}
+}`
+
+type swaggerInfo struct {
+	Version     string
+	Host        string
+	BasePath    string
+	Schemes     []string
+	Title       string
+	Description string
+}
+
+// SwaggerInfo holds exported Swagger Info so clients can modify it
+var SwaggerInfo = swaggerInfo{
+	Version:     "1.0",
+	Host:        "127.0.0.1:8607",
+	BasePath:    "/",
+	Schemes:     []string{},
+	Title:       "弘则人力资源管理系统API接口文档",
+	Description: "弘则人力资源管理系统API接口文档",
+}
+
+type s struct{}
+
+func (s *s) ReadDoc() string {
+	sInfo := SwaggerInfo
+	sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
+
+	t, err := template.New("swagger_info").Funcs(template.FuncMap{
+		"marshal": func(v interface{}) string {
+			a, _ := json.Marshal(v)
+			return string(a)
+		},
+		"escape": func(v interface{}) string {
+			// escape tabs
+			str := strings.Replace(v.(string), "\t", "\\t", -1)
+			// replace " with \", and if that results in \\", replace that with \\\"
+			str = strings.Replace(str, "\"", "\\\"", -1)
+			return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1)
+		},
+	}).Parse(doc)
+	if err != nil {
+		return doc
+	}
+
+	var tpl bytes.Buffer
+	if err := t.Execute(&tpl, sInfo); err != nil {
+		return doc
+	}
+
+	return tpl.String()
+}
+
+func init() {
+	swag.Register("swagger", &s{})
+}

+ 21 - 0
docs/swagger.json

@@ -0,0 +1,21 @@
+{
+    "swagger": "2.0",
+    "info": {
+        "description": "弘则人力资源管理系统API接口文档",
+        "title": "弘则人力资源管理系统API接口文档",
+        "termsOfService": "https://www.hzinsights.com/",
+        "contact": {
+            "name": "www.hzinsights.com/",
+            "url": "https://www.hzinsights.com/",
+            "email": "pyan@hzinsights.com"
+        },
+        "license": {
+            "name": "Apache 2.0",
+            "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+        },
+        "version": "1.0"
+    },
+    "host": "127.0.0.1:8607",
+    "basePath": "/",
+    "paths": {}
+}

+ 16 - 0
docs/swagger.yaml

@@ -0,0 +1,16 @@
+basePath: /
+host: 127.0.0.1:8607
+info:
+  contact:
+    email: pyan@hzinsights.com
+    name: www.hzinsights.com/
+    url: https://www.hzinsights.com/
+  description: 弘则人力资源管理系统API接口文档
+  license:
+    name: Apache 2.0
+    url: http://www.apache.org/licenses/LICENSE-2.0.html
+  termsOfService: https://www.hzinsights.com/
+  title: 弘则人力资源管理系统API接口文档
+  version: "1.0"
+paths: {}
+swagger: "2.0"

+ 60 - 0
global/global.go

@@ -0,0 +1,60 @@
+package global
+
+import (
+	"fmt"
+	"github.com/fsnotify/fsnotify"
+	"github.com/go-redis/redis/v8"
+	"github.com/olivere/elastic/v7"
+	oplogging "github.com/op/go-logging"
+	"github.com/spf13/viper"
+	"gorm.io/gorm"
+	"hongze/hrms_api/config"
+	"hongze/hrms_api/utils"
+	"io"
+)
+
+var (
+	CONFIG        config.Config //配置文件
+	LOG           *oplogging.Logger
+	MYSQL         map[string]*gorm.DB //数据库连接配置
+	MYSQL_LOG     io.Writer
+	DEFAULT_MYSQL *gorm.DB      //默认数据库连接配置
+	Redis         *redis.Client //redis链接
+	EsClient      *elastic.Client
+)
+
+const ConfigFile = "config/config.yaml"                               //本地(测试)环境下的配置文件地址
+const ProConfigFile = "/home/code/config/fmms_api/config/config.yaml" //生产环境下的配置文件地址
+
+func init() {
+	v := viper.New()
+
+	configFilePath := ConfigFile
+
+	//如果不存在该配置文件,那么应该是线上环境,那么去寻找线上配置文件的路径
+	if !utils.FileIsExist(configFilePath) {
+		configFilePath = ProConfigFile
+	}
+
+	//设置配置文件
+	v.SetConfigFile(configFilePath)
+
+	err := v.ReadInConfig()
+	if err != nil {
+		panic(fmt.Errorf("读取配置失败,Err: %s \n", err))
+	}
+
+	//持续监听文件
+	v.WatchConfig()
+
+	v.OnConfigChange(func(e fsnotify.Event) {
+		fmt.Println("配置文件变更:", e.Name)
+		if err := v.Unmarshal(&CONFIG); err != nil {
+			fmt.Println("配置重赋值失败,Err:", err)
+		}
+		fmt.Println(CONFIG)
+	})
+	if err := v.Unmarshal(&CONFIG); err != nil {
+		fmt.Println("配置初始化赋值失败,Err:", err)
+	}
+}

+ 46 - 0
global/validator.go

@@ -0,0 +1,46 @@
+package global
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin/binding"
+	"github.com/go-playground/locales/en"
+	"github.com/go-playground/locales/zh"
+	ut "github.com/go-playground/universal-translator"
+	"github.com/go-playground/validator/v10"
+	enTranslations "github.com/go-playground/validator/v10/translations/en"
+	zhTranslations "github.com/go-playground/validator/v10/translations/zh"
+)
+
+// 定义一个全局翻译器T
+var Trans ut.Translator
+
+// InitTrans 初始化翻译器
+func InitTrans(locale string) (err error) {
+	// 修改gin框架中的Validator引擎属性,实现自定制
+	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
+		zhT := zh.New() // 中文翻译器
+		enT := en.New() // 英文翻译器
+		// 第一个参数是备用(fallback)的语言环境
+		// 后面的参数是应该支持的语言环境(支持多个)
+		// uni := ut.New(zhT, zhT) 也是可以的
+		uni := ut.New(enT, zhT, enT)
+		// locale 通常取决于 http 请求头的 'Accept-Language'
+		var ok bool
+		// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
+		Trans, ok = uni.GetTranslator(locale)
+		if !ok {
+			return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
+		}
+		// 注册翻译器
+		switch locale {
+		case "en":
+			err = enTranslations.RegisterDefaultTranslations(v, Trans)
+		case "zh":
+			err = zhTranslations.RegisterDefaultTranslations(v, Trans)
+		default:
+			err = enTranslations.RegisterDefaultTranslations(v, Trans)
+		}
+		return
+	}
+	return
+}

+ 32 - 0
go.mod

@@ -0,0 +1,32 @@
+module hongze/hrms_api
+
+go 1.15
+
+require (
+	github.com/aliyun/alibaba-cloud-sdk-go v1.61.1754
+	github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible
+	github.com/emersion/go-imap v1.2.1
+	github.com/emersion/go-message v0.15.0
+	github.com/fsnotify/fsnotify v1.5.1
+	github.com/gin-gonic/gin v1.7.4
+	github.com/go-playground/locales v0.14.0
+	github.com/go-playground/universal-translator v0.18.0
+	github.com/go-playground/validator/v10 v10.9.0
+	github.com/go-redis/redis/v8 v8.11.4
+	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
+	github.com/jonboulle/clockwork v0.2.2 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
+	github.com/lestrrat-go/strftime v1.0.5 // indirect
+	github.com/mattn/go-isatty v0.0.14 // indirect
+	github.com/olivere/elastic/v7 v7.0.31
+	github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
+	github.com/rdlucklib/rdluck_tools v1.0.3
+	github.com/spf13/viper v1.9.0
+	github.com/swaggo/gin-swagger v1.3.3
+	github.com/swaggo/swag v1.7.4
+	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
+	golang.org/x/image v0.0.0-20190802002840-cff245a6509b
+	gorm.io/driver/mysql v1.1.3
+	gorm.io/gorm v1.22.2
+)

+ 73 - 0
init_serve/mysql.go

@@ -0,0 +1,73 @@
+package init_serve
+
+import (
+	"fmt"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+	"gorm.io/gorm/logger"
+	"hongze/hrms_api/global"
+	"io"
+	"log"
+	"os"
+	"time"
+)
+
+// Mysql 数据库初始化
+func Mysql() {
+	mysqlConf := global.CONFIG.Mysql
+	if len(mysqlConf.List) <= 0 {
+		global.LOG.Error("mysql链接未配置")
+		panic(fmt.Errorf("mysql链接未配置"))
+	}
+
+	//开启日志
+	logWriter := io.MultiWriter(global.MYSQL_LOG) //binlog日志,记录到文件中去
+	if global.CONFIG.Mysql.Stdout {
+		logWriter = io.MultiWriter(global.MYSQL_LOG, os.Stdout)
+	}
+	newLogger := logger.New(log.New(logWriter, "\r\n", log.LstdFlags), logger.Config{
+		SlowThreshold:             200 * time.Millisecond, //慢sql :200ms
+		LogLevel:                  logger.Info,            //记录的日志类型,info代表所有信息都记录
+		IgnoreRecordNotFoundError: true,                   //是否忽略找不到数据错误信息(只是日志记录记录成err还是普通的输出的区别,并不影响业务代码中的:找不到数据行error)
+		Colorful:                  true,                   //是否颜色输出
+	})
+
+	mysqlMap := make(map[string]*gorm.DB)
+	for _, conf := range mysqlConf.List {
+		dsn := conf.Dsn
+
+		db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
+			Logger: newLogger,
+		})
+		if err != nil {
+			global.LOG.Errorf("mysql 启动异常,数据库:", conf.AliasName, ";Err:", err)
+			panic(fmt.Errorf("mysql 启动异常,数据库:", conf.AliasName, "Err:%s", err))
+		}
+
+		//创建连接池
+		sqlDB, err := db.DB()
+		if err != nil {
+			global.LOG.Errorf("mysql 创建连接池失败,数据库:", conf.AliasName, ";Err:", err)
+			panic(fmt.Errorf("mysql 创建连接池失败,数据库:", conf.AliasName, "Err:%s", err))
+		}
+
+		mysqlMap[conf.AliasName] = db
+
+		//默认数据库连接
+		if mysqlConf.DefaultDsnAliasName == conf.AliasName {
+			global.DEFAULT_MYSQL = db
+		}
+
+		// SetMaxIdleConns 设置空闲连接池中连接的最大数量
+		sqlDB.SetMaxIdleConns(conf.MaxIdleConns)
+
+		// SetMaxOpenConns 设置打开数据库连接的最大数量。
+		sqlDB.SetMaxOpenConns(conf.MaxOpenConns)
+
+		// SetConnMaxLifetime 设置了连接可复用的最大时间。
+		//sqlDB.SetConnMaxLifetime(time.Hour)
+	}
+
+	//全局赋值数据库链接
+	global.MYSQL = mysqlMap
+}

+ 25 - 0
init_serve/redis.go

@@ -0,0 +1,25 @@
+package init_serve
+
+import (
+	"context"
+	"github.com/go-redis/redis/v8"
+	"hongze/hrms_api/global"
+)
+
+func Redis() {
+	redisConf := global.CONFIG.Redis
+	client := redis.NewClient(&redis.Options{
+		Addr:     redisConf.Address,
+		Password: redisConf.Password,
+		DB:       redisConf.Db,
+		//PoolSize: 10, //连接池最大socket连接数,默认为10倍CPU数, 10 * runtime.NumCPU(暂不配置)
+	})
+	_, err := client.Ping(context.TODO()).Result()
+	if err != nil {
+		global.LOG.Error("redis 链接失败:", err)
+		panic("redis 链接失败:" + err.Error())
+	}
+
+	//全局赋值redis链接
+	global.Redis = client
+}

+ 36 - 0
init_serve/router.go

@@ -0,0 +1,36 @@
+package init_serve
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/swaggo/gin-swagger"
+	"github.com/swaggo/gin-swagger/swaggerFiles"
+	_ "hongze/hrms_api/docs"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/middleware"
+	"hongze/hrms_api/routers"
+)
+
+// InitRouter 初始化路由
+func InitRouter() (r *gin.Engine) {
+	//设置
+	gin.SetMode(global.CONFIG.Serve.RunMode)
+	// 1.创建路由
+	r = gin.Default()
+	r.Use(middleware.Cors())
+	//r.Use(gin.Recovery())
+	r.Use(middleware.Recover())
+
+	// 公共的中间件
+	r.Use(middleware.Common())
+
+	//swagger界面访问地址 http://localhost:8390/swagger/index.html
+	r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
+	rBase := r.Group("api/")
+	//系统相关路由
+	systemGroup := rBase.Group("system/")
+	resourceGroup := rBase.Group("resource/")
+	routers.InitSystem(systemGroup)
+	routers.InitAuth(rBase)
+	routers.InitResource(resourceGroup)
+	return
+}

+ 5 - 0
init_serve/task.go

@@ -0,0 +1,5 @@
+package init_serve
+
+func InitTask() {
+
+}

+ 0 - 0
logic/logic.txt


+ 23 - 0
main.go

@@ -0,0 +1,23 @@
+package main
+
+import (
+	"hongze/hrms_api/core"
+)
+
+// @title 弘则人力资源管理系统API接口文档
+// @version 1.0
+// @description 弘则人力资源管理系统API接口文档
+// @termsOfService https://www.hzinsights.com/
+
+// @contact.name www.hzinsights.com/
+// @contact.url https://www.hzinsights.com/
+// @contact.email pyan@hzinsights.com
+
+// @license.name Apache 2.0
+// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
+
+// @host 127.0.0.1:8390
+// @BasePath /
+func main() {
+	core.RunServe()
+}

+ 27 - 0
middleware/common.go

@@ -0,0 +1,27 @@
+package middleware
+
+import (
+	"github.com/gin-gonic/gin"
+	"strconv"
+)
+
+// Common 公共中间件
+func Common() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		var currPage, pageSize int
+		reqPage := c.DefaultQuery("curr_page", "0")
+		currPage, _ = strconv.Atoi(reqPage)
+		if currPage <= 0 {
+			currPage = 1
+		}
+
+		reqPageSize := c.DefaultQuery("page_size", "0")
+		pageSize, _ = strconv.Atoi(reqPageSize)
+		if pageSize <= 0 {
+			pageSize = 20
+		}
+		c.Set("curr_page", currPage)
+		c.Set("page_size", pageSize)
+		c.Next()
+	}
+}

+ 27 - 0
middleware/cors.go

@@ -0,0 +1,27 @@
+package middleware
+
+import (
+	"github.com/gin-gonic/gin"
+	"net/http"
+)
+
+// Cors 处理跨域请求,支持options访问
+func Cors() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		method := c.Request.Method
+
+		c.Header("Access-Control-Allow-Origin", "*")
+		//c.Header("Access-Control-Allow-Origin", c.Request.Referer())
+		c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id")
+		c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
+		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
+		c.Header("Access-Control-Allow-Credentials", "true")
+
+		// 放行所有OPTIONS方法
+		if method == "OPTIONS" {
+			c.AbortWithStatus(http.StatusNoContent)
+		}
+		// 处理请求
+		c.Next()
+	}
+}

+ 57 - 0
middleware/recover.go

@@ -0,0 +1,57 @@
+package middleware
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"hongze/hrms_api/controller/resp"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/services/alarm_msg"
+	"hongze/hrms_api/utils"
+	"net/http"
+	"runtime"
+	"time"
+)
+
+// Recover 异常处理
+func Recover() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		contentType := c.ContentType()
+		// 为 multipart forms 设置较低的内存限制(50M) (默认是 32 MiB)
+		if contentType == "multipart/form-data" {
+			err := c.Request.ParseMultipartForm(10 << 20)
+			if err != nil {
+				resp.Custom(http.StatusRequestEntityTooLarge, "上传文件太大,err:"+err.Error(), c)
+				c.Abort()
+				return
+			}
+		}
+		defer func() {
+			if err := recover(); err != nil {
+				stack := ""
+
+				msg := fmt.Sprintf("The request url is  %v", c.Request.RequestURI)
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				msg = fmt.Sprintf("The request data is %v", c.Params)
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				msg = fmt.Sprintf("Handler crashed with error %v", err)
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				for i := 1; ; i++ {
+					_, file, line, ok := runtime.Caller(i)
+					if !ok {
+						break
+					}
+					global.LOG.Critical(fmt.Sprintf("%s:%d", file, line))
+					stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d</br>", file, line))
+				}
+				fmt.Println("stack:", stack)
+				resp.Custom(http.StatusInternalServerError, "系统异常", c)
+				go alarm_msg.SendAlarmMsg(utils.APPNAME+"崩了"+time.Now().Format(utils.FormatDateTime)+";Err:"+stack, 3)
+				return
+			}
+		}()
+		c.Next()
+	}
+}

+ 81 - 0
middleware/token.go

@@ -0,0 +1,81 @@
+package middleware
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"hongze/hrms_api/controller/resp"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/system"
+	"hongze/hrms_api/utils"
+	"time"
+)
+
+func Token() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		token := c.Request.Header.Get("Authorization")
+		if token == "" {
+			token = c.DefaultQuery("authorization", "")
+			if token == "" {
+				token = c.DefaultQuery("Authorization", "")
+			}
+		}
+		if token == "" {
+			resp.TokenError(nil, "未登录或非法访问", "未登录或非法访问", c)
+			c.Abort()
+			return
+		}
+		sessionInfo := new(system.LoginTokenContent)
+		content, _ := global.Redis.Get(context.TODO(), utils.SYSTEM_LOGIN_TOKEN+token).Result()
+		if content == "" {
+			resp.TokenError(nil, "信息已变更,请重新登陆!", "找不到对应session", c)
+			c.Abort()
+			return
+		}
+
+		err := json.Unmarshal([]byte(content), &sessionInfo)
+		if sessionInfo == nil {
+			resp.TokenError(nil, "信息已变更,请重新登陆!", "session解析失败", c)
+			c.Abort()
+			return
+		}
+
+		admin := new(system.SysAdmin)
+		admin, err = admin.GetAdminByAdminId(sessionInfo.AdminId)
+		if err != nil {
+			if err == utils.ErrNoRow {
+				resp.TokenError(nil, "信息已变更,请重新登陆!", "找不到对应账号", c)
+				c.Abort()
+				return
+			}
+			resp.TokenError(nil, "网络异常,请稍后重试!", err.Error(), c)
+			c.Abort()
+			return
+		}
+		if admin.Enabled == 0 {
+			resp.SpecificFail(resp.HASFORBIDDEN_CODE, nil, "您的账号已被禁用,如需登录,请联系管理员", c)
+			c.Abort()
+			return
+		}
+		/*if admin.Password != sessionInfo.Password {
+			resp.SpecificFail(resp.PASSWORDCHANGE_CODE, nil, "信息已变更,请重新登陆!", c)
+			c.Abort()
+			return
+		}*/
+
+		//更新token的有效期,重置为30秒
+		global.Redis.SetEX(context.TODO(), utils.SYSTEM_LOGIN_TOKEN+token, content, 120*time.Minute)
+
+		// 不信任名单也同步更新
+		if !sessionInfo.IsRemember {
+			noTrustLoginKey := fmt.Sprint(utils.SYSTEM_LOGIN_TOKEN_NO_TRUST, admin.AdminId)
+			tokenContent, _ := global.Redis.Get(context.TODO(), noTrustLoginKey).Result()
+			if tokenContent != "" {
+				global.Redis.SetEX(context.TODO(), noTrustLoginKey, tokenContent, 120*time.Minute)
+			}
+		}
+		c.Set("adminInfo", admin)
+		c.Next()
+	}
+}

+ 80 - 0
middleware/token_no_login.go

@@ -0,0 +1,80 @@
+package middleware
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hrms_api/controller/resp"
+)
+
+func TokenNoLogin() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		token := c.Request.Header.Get("Authorization")
+		if token == "" {
+			token = c.GetString("authorization")
+			if token == "" {
+				token = c.GetString("Authorization")
+			}
+		}
+		if token == "" {
+			resp.TokenError(nil, "未登录或非法访问", "token为空", c)
+			c.Abort()
+			return
+		}
+		//sessionInfo, err := session.GetTokenByToken(token)
+		//if err != nil {
+		//	if err == utils.ErrNoRow {
+		//		resp.TokenError(nil, "信息已变更,请重新登陆!", "找不到session", c)
+		//		c.Abort()
+		//		return
+		//	}
+		//	resp.TokenError(nil, "网络异常,请稍后重试!", err.Error(), c)
+		//	c.Abort()
+		//	return
+		//}
+		//
+		//if sessionInfo == nil {
+		//	resp.TokenError(nil, "网络异常,请稍后重试!", "session为空", c)
+		//	c.Abort()
+		//	return
+		//}
+		//
+		//var userInfo services.UserInfo
+		//
+		//if sessionInfo.OpenID != "" {
+		//	tmpUserInfo, tmpErr := services.GetWxUserItemByOpenId(sessionInfo.OpenID)
+		//	userInfo = tmpUserInfo
+		//	err = tmpErr
+		//	if err != nil && err != services.ERR_NO_USER_RECORD && err != services.ERR_USER_NOT_BIND{
+		//		resp.TokenError(nil, "数据异常!", "openid查询用户信息错误", c)
+		//		c.Abort()
+		//		return
+		//	}
+		//} else {
+		//	//判断pc端登录的情况
+		//	tmpUserInfo, tmpErr := services.GetWxUserItemByUserId(int(sessionInfo.UserID), 3)
+		//	userInfo = tmpUserInfo
+		//	err = tmpErr
+		//	if err != nil {
+		//		resp.TokenError(nil, "数据异常!", "userID查询用户信息错误", c)
+		//		c.Abort()
+		//		return
+		//	}
+		//}
+		//
+		////如果查询异常,且异常信息不是:用户openid查询出来发现没有绑定用户
+		//if err != nil && err != services.ERR_USER_NOT_BIND {
+		//	//没有找到记录
+		//	if err == utils.ErrNoRow {
+		//		resp.TokenError(nil, "信息已变更,请重新登陆!", err.Error(), c)
+		//		c.Abort()
+		//		return
+		//	}
+		//
+		//	resp.TokenError(nil, "网络异常,请稍后重试!", err.Error(), c)
+		//	c.Abort()
+		//	return
+		//}
+		//
+		//c.Set("userInfo", userInfo)
+		c.Next()
+	}
+}

+ 171 - 0
models/base/base.go

@@ -0,0 +1,171 @@
+package base
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"gorm.io/gorm"
+)
+
+var globalIsRelated bool = true // 全局预加载
+
+// prepare for other
+type BaseMgr struct {
+	*gorm.DB
+	Ctx       context.Context
+	cancel    context.CancelFunc
+	timeout   time.Duration
+	isRelated bool
+}
+
+// SetTimeOut set timeout
+func (obj *BaseMgr) SetTimeOut(timeout time.Duration) {
+	obj.Ctx, obj.cancel = context.WithTimeout(context.Background(), timeout)
+	obj.timeout = timeout
+}
+
+// SetCtx set context
+func (obj *BaseMgr) SetCtx(c context.Context) {
+	if c != nil {
+		obj.Ctx = c
+	}
+}
+
+// GetCtx get context
+func (obj *BaseMgr) GetCtx() context.Context {
+	return obj.Ctx
+}
+
+// Cancel cancel context
+func (obj *BaseMgr) Cancel(c context.Context) {
+	obj.cancel()
+}
+
+// GetDB get gorm.DB info
+func (obj *BaseMgr) GetDB() *gorm.DB {
+	return obj.DB
+}
+
+// UpdateDB update gorm.DB info
+func (obj *BaseMgr) UpdateDB(db *gorm.DB) {
+	obj.DB = db
+}
+
+// GetIsRelated Query foreign key Association.获取是否查询外键关联(gorm.Related)
+func (obj *BaseMgr) GetIsRelated() bool {
+	return obj.isRelated
+}
+
+// SetIsRelated Query foreign key Association.设置是否查询外键关联(gorm.Related)
+func (obj *BaseMgr) SetIsRelated(b bool) {
+	obj.isRelated = b
+}
+
+// New new gorm.新gorm,重置条件
+func (obj *BaseMgr) New() {
+	obj.DB = obj.NewDB()
+}
+
+// NewDB new gorm.新gorm
+func (obj *BaseMgr) NewDB() *gorm.DB {
+	return obj.DB.Session(&gorm.Session{NewDB: true, Context: obj.Ctx})
+}
+
+type Options struct {
+	Query map[string]interface{}
+}
+
+// Option overrides behavior of Connect.
+type Option interface {
+	Apply(*Options)
+}
+
+type OptionFunc func(*Options)
+
+func (f OptionFunc) apply(o *Options) {
+	f(o)
+}
+
+// OpenRelated 打开全局预加载
+func OpenRelated() {
+	globalIsRelated = true
+}
+
+// CloseRelated 关闭全局预加载
+func CloseRelated() {
+	globalIsRelated = true
+}
+
+// 自定义sql查询
+type Condition struct {
+	list []*conditionInfo
+}
+
+func (c *Condition) AndWithCondition(condition bool, column string, cases string, value interface{}) *Condition {
+	if condition {
+		c.list = append(c.list, &conditionInfo{
+			andor:  "and",
+			column: column, // 列名
+			case_:  cases,  // 条件(and,or,in,>=,<=)
+			value:  value,
+		})
+	}
+	return c
+}
+
+// And a Condition by and .and 一个条件
+func (c *Condition) And(column string, cases string, value interface{}) *Condition {
+	return c.AndWithCondition(true, column, cases, value)
+}
+
+func (c *Condition) OrWithCondition(condition bool, column string, cases string, value interface{}) *Condition {
+	if condition {
+		c.list = append(c.list, &conditionInfo{
+			andor:  "or",
+			column: column, // 列名
+			case_:  cases,  // 条件(and,or,in,>=,<=)
+			value:  value,
+		})
+	}
+	return c
+}
+
+// Or a Condition by or .or 一个条件
+func (c *Condition) Or(column string, cases string, value interface{}) *Condition {
+	return c.OrWithCondition(true, column, cases, value)
+}
+
+func (c *Condition) Get() (where string, out []interface{}) {
+	firstAnd := -1
+	for i := 0; i < len(c.list); i++ { // 查找第一个and
+		if c.list[i].andor == "and" {
+			where = fmt.Sprintf("`%v` %v ?", c.list[i].column, c.list[i].case_)
+			out = append(out, c.list[i].value)
+			firstAnd = i
+			break
+		}
+	}
+
+	if firstAnd < 0 && len(c.list) > 0 { // 补刀
+		where = fmt.Sprintf("`%v` %v ?", c.list[0].column, c.list[0].case_)
+		out = append(out, c.list[0].value)
+		firstAnd = 0
+	}
+
+	for i := 0; i < len(c.list); i++ { // 添加剩余的
+		if firstAnd != i {
+			where += fmt.Sprintf(" %v `%v` %v ?", c.list[i].andor, c.list[i].column, c.list[i].case_)
+			out = append(out, c.list[i].value)
+		}
+	}
+
+	return
+}
+
+type conditionInfo struct {
+	andor  string
+	column string // 列名
+	case_  string // 条件(in,>=,<=)
+	value  interface{}
+}

+ 178 - 0
models/base/page.go

@@ -0,0 +1,178 @@
+package base
+
+import (
+	"fmt"
+	"strings"
+)
+
+type IPage interface {
+	GetTotal() int64             //获取总记录数
+	SetTotal(int64)              //设置总记录数
+	GetCurrent() int64           //获取当前页
+	SetCurrent(int64)            //设置当前页
+	GetPageSize() int64          //获取每页显示大小
+	SetPageSize(int64)           //设置每页显示大小
+	AddOrderItem(OrderItem)      // 设置排序条件
+	AddOrderItems([]OrderItem)   // 批量设置排序条件
+	GetOrderItemsString() string // 将排序条件拼接成字符串
+	Offset() int64               // 获取偏移量
+	GetPages() int64             // 获取总的分页数
+}
+
+type OrderItem struct {
+	Column string // 需要排序的字段
+	Asc    bool   // 是否正序排列,默认true
+}
+
+type BaseData struct {
+	Page *Page        `json:"page"`
+	List interface{} `json:"list"` // 查询数据列表
+}
+
+type BaseOnlyData struct {
+	List interface{} `json:"list"` // 查询数据列表
+}
+
+type Page struct {
+	Total    int64       `json:"total"`     // 总的记录数
+	Pages    int64       `json:"pages"`     //总页数
+	PageSize int64       `json:"page_size"` // 每页显示的大小
+	Current  int64       `json:"current"`   // 当前页
+	orders   []OrderItem `json:"orders"`    // 排序条件
+}
+
+type PageReq struct {
+	PageSize int64 `json:"page_size" form:"page_size,default=20"`
+	Current  int64 `json:"current" form:"current,default=1"`
+}
+
+func (b *BaseData) GetList() interface{} {
+	return b.List
+}
+
+func (b *BaseData) SetList(list interface{}) {
+	b.List = list
+}
+
+
+func (bo *BaseOnlyData) GetList() interface{} {
+	return bo.List
+}
+
+func (bo *BaseOnlyData) SetList(list interface{}) {
+	bo.List = list
+}
+
+func (b *BaseData) GetPage() *Page {
+	return b.Page
+}
+func (b *BaseData) SetPage(page *Page) {
+	b.Page = page
+}
+
+func (page *Page) GetTotal() int64 {
+	return page.Total
+}
+
+func (page *Page) SetTotal(total int64) {
+	page.Total = total
+	page.SetPages()
+}
+
+func (page *Page) GetCurrent() int64 {
+	return page.Current
+}
+
+func (page *Page) SetCurrent(current int64) {
+	page.Current = current
+}
+
+func (page *Page) GetPageSize() int64 {
+	return page.PageSize
+}
+func (page *Page) SetPageSize(size int64) {
+	page.PageSize = size
+}
+
+func (page *Page) AddOrderItem(orderItem OrderItem) {
+	page.orders = append(page.orders, orderItem)
+}
+
+func (page *Page) AddOrderItems(orderItems []OrderItem) {
+	page.orders = append(page.orders, orderItems...)
+}
+
+func (page *Page) GetOrderItemsString() string {
+	arr := make([]string, 0)
+	var order string
+
+	for _, val := range page.orders {
+		if val.Asc {
+			order = ""
+		} else {
+			order = "desc"
+		}
+		arr = append(arr, fmt.Sprintf("%s %s", val.Column, order))
+	}
+	return strings.Join(arr, ",")
+}
+
+func (page *Page) Offset() int64 {
+	if page.GetCurrent() > 0 {
+		return (page.GetCurrent() - 1) * page.GetPageSize()
+	} else {
+		return 0
+	}
+}
+
+func (page *Page) GetPages() int64 {
+	if page.GetPageSize() == 0 {
+		return 0
+	}
+	pages := page.GetTotal() / page.GetPageSize()
+	if page.GetTotal()%page.PageSize != 0 {
+		pages++
+	}
+
+	return pages
+}
+
+func (page *Page) SetPages() {
+	if page.GetPageSize() == 0 {
+		page.Pages = 0
+	} else {
+		pages := page.GetTotal() / page.GetPageSize()
+		if page.GetTotal()%page.PageSize != 0 {
+			pages++
+		}
+		page.Pages = pages
+	}
+}
+
+func BuildAsc(column string) OrderItem {
+	return OrderItem{Column: column, Asc: true}
+}
+
+func BuildDesc(column string) OrderItem {
+	return OrderItem{Column: column, Asc: false}
+}
+
+func BuildAscs(columns ...string) []OrderItem {
+	items := make([]OrderItem, 0)
+	for _, val := range columns {
+		items = append(items, BuildAsc(val))
+	}
+	return items
+}
+
+func BuildDescs(columns ...string) []OrderItem {
+	items := make([]OrderItem, 0)
+	for _, val := range columns {
+		items = append(items, BuildDesc(val))
+	}
+	return items
+}
+
+func NewPage(pageSize, current int64, orderItems ...OrderItem) *Page {
+	return &Page{PageSize: pageSize, Current: current, orders: orderItems}
+}

+ 13 - 0
models/base/time_base.go

@@ -0,0 +1,13 @@
+package base
+
+import "time"
+
+type TimeBase struct {
+	CreateTime time.Time `gorm:"autoCreateTime;column:create_time" json:"create_time"`       //创建时间
+	ModifyTime time.Time `gorm:"autoUpdateTime:milli;column:modify_time" json:"modify_time"` //最后更新时间
+}
+
+func (item *TimeBase) Set() {
+	item.CreateTime = time.Now()
+	item.ModifyTime = time.Now()
+}

+ 197 - 0
models/system/sys_admin.go

@@ -0,0 +1,197 @@
+package system
+
+import (
+	"context"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/base"
+	"time"
+)
+
+// 管理员表
+type SysAdmin struct {
+	AdminId       uint64    `gorm:"primaryKey;column:admin_id" json:"admin_id"` //账号ID
+	AdminName     string    `gorm:"column:admin_name" json:"admin_name"`        //账号名
+	AdminAvatar   string    `gorm:"column:admin_avatar" json:"admin_avatar"`    //用户头像
+	RealName      string    `gorm:"column:real_name" json:"real_name"`          //真实姓名
+	Password      string    `gorm:"column:password" json:"password"`            //密码
+	Enabled       int8      `gorm:"column:enabled" json:"enabled"`              //1:有效,0:禁用
+	Email         string    `gorm:"column:email" json:"email"`
+	LastLoginTime time.Time `gorm:"column:last_login_time" json:"last_login_time"` //最近登陆时间
+	Mobile        string    `gorm:"column:mobile" json:"mobile"`                   //手机号
+	DeptId        int64     `gorm:"column:dept_id" json:"dept_id"`                 //部门id
+	DisableTime   time.Time `gorm:"column:disable_time" json:"disable_time"`       //禁用时间
+	RoleId        int64     `gorm:"column:role_id" json:"role_id"`                 //角色ID
+	Position      string    `gorm:"column:position" json:"position"`               //职位
+	Remark        string    `gorm:"column:remark" json:"remark"`                   //备注
+	base.TimeBase
+}
+
+// TableName get sql table name.获取数据库表名
+func (a *SysAdmin) TableName() string {
+	return "sys_admin"
+}
+
+// Add 新增
+func (a *SysAdmin) Add() (err error) {
+	err = global.DEFAULT_MYSQL.Create(a).Error
+	return
+}
+
+type SysAdminAddReq struct {
+	AdminName string `json:"admin_name" binding:"required"`           //账号名
+	RealName  string `json:"real_name"  binding:"required"`           //真实姓名
+	Password  string `json:"password" binding:"required"`             //密码
+	Enabled   int8   `json:"enabled" binding:"oneof=0 1"`             //账号状态:1:有效,0:禁用
+	Email     string `json:"email" binding:"omitempty,email"`         //邮箱
+	Mobile    string `json:"mobile" binding:"required,number,len=11"` //手机号
+	DeptId    int64  `json:"dept_id" binding:"required,gte=1"`        //部门id
+	RoleId    int64  `json:"role_id" binding:"gte=1"`                 //角色ID
+	Position  string `json:"position"`                                //职位
+	Remark    string `json:"remark"`                                  //备注
+}
+
+type SysAdminEditReq struct {
+	AdminId   uint64 `json:"admin_id" binding:"required,gte=1"`       //账号ID
+	AdminName string `json:"admin_name" binding:"required"`           //账号名
+	RealName  string `json:"real_name"  binding:"required"`           //真实姓名
+	Enabled   int8   `json:"enabled" binding:"oneof=0 1"`             //账号状态:1:有效,0:禁用
+	Email     string `json:"email" binding:"omitempty,email"`         //邮箱
+	Mobile    string `json:"mobile" binding:"required,number,len=11"` //手机号
+	DeptId    int64  `json:"dept_id" binding:"required,gte=1"`        //部门id
+	RoleId    int64  `json:"role_id" binding:"gte=1"`                 //角色ID
+	Position  string `json:"position"`                                //职位
+	Remark    string `json:"remark"`                                  //备注
+}
+
+func (a *SysAdmin) GetAdminByAdminName(adminName string) (item *SysAdmin, err error) {
+	err = global.DEFAULT_MYSQL.Model(a).Where("admin_name = ?", adminName).First(&item).Error
+	return
+}
+
+func (a *SysAdmin) GetAdminByMobile(mobile string) (item *SysAdmin, err error) {
+	err = global.DEFAULT_MYSQL.Model(a).Where("mobile = ?", mobile).First(&item).Error
+	return
+}
+
+// 修改
+func (a *SysAdmin) Update(updateCols []string) (err error) {
+	err = global.DEFAULT_MYSQL.Model(a).Select(updateCols).Updates(a).Error
+	return
+}
+func (a *SysAdmin) UpdateByCondition(condition string, pars []interface{}, updates map[string]interface{}) (err error) {
+	err = global.DEFAULT_MYSQL.Model(a).Where(condition, pars...).Updates(updates).Error
+	return
+}
+
+// SelectPage 分页查询
+func (a *SysAdmin) SelectPage(page base.IPage, condition string, pars []interface{}) (count int64, results []SysAdminListTmpItem, err error) {
+	query := global.DEFAULT_MYSQL.WithContext(context.TODO()).Model(a).
+		Select("sys_admin.*, r.role_name").
+		Joins("Left JOIN sys_role r ON r.role_id = sys_admin.role_id").
+		Where(condition, pars...)
+	query.Count(&count)
+	if len(page.GetOrderItemsString()) > 0 {
+		query = query.Order(page.GetOrderItemsString())
+	}
+	err = query.Limit(int(page.GetPageSize())).Offset(int(page.Offset())).Find(&results).Error
+	return
+}
+
+type SysAdminListReq struct {
+	DeptId  int64  `json:"dept_id" form:"dept_id"` //部门id
+	KeyWord string `json:"key_word" form:"key_word"`
+	Enabled string `json:"enabled"  form:"enabled"` //-1全部,1:有效,0:禁用
+	base.PageReq
+}
+
+type SysAdminListTmpItem struct {
+	AdminId      uint64    `json:"admin_id"`       //账号id
+	AdminName    string    `json:"admin_name"`     //账号名
+	RealName     string    `json:"real_name"`      //真实姓名
+	Enabled      int8      `json:"enabled"`        //1:有效,0:禁用
+	Email        string    `json:"email"`          //邮箱
+	CreateTime   time.Time `json:"create_time"`    //创建时间
+	ModifyTime   time.Time `json:"modify_time"`    //最后更新时间
+	Mobile       string    `json:"mobile"`         //手机号
+	DeptId       int64     `json:"dept_id"`        //部门id
+	RoleId       int64     `json:"role_id"`        //角色ID
+	RoleName     string    `json:"role_name"`      //角色名称
+	DeptFullName string    `json:"dept_full_name"` //部门全称
+	Position     string    `json:"position"`       //职位
+	Remark       string    `json:"remark"`         //备注
+}
+
+type SysAdminListItem struct {
+	AdminId      uint64 `json:"admin_id"`       //账号id
+	AdminName    string `json:"admin_name"`     //账号名
+	RealName     string `json:"real_name"`      //真实姓名
+	Enabled      int8   `json:"enabled"`        //1:有效,0:禁用
+	Email        string `json:"email"`          //邮箱
+	Mobile       string `json:"mobile"`         //手机号
+	DeptId       int64  `json:"dept_id"`        //部门id
+	RoleId       int64  `json:"role_id"`        //角色ID
+	RoleName     string `json:"role_name"`      //角色名称
+	DeptFullName string `json:"dept_full_name"` //部门全称
+	CreateTime   string `json:"create_time"`    //创建时间
+	ModifyTime   string `json:"modify_time"`    //最后更新时间
+	Position     string `json:"position"`       //职位
+	Remark       string `json:"remark"`         //备注
+}
+
+// GetAdminByAdminId 根据adminId获取用户信息
+func (a *SysAdmin) GetAdminByAdminId(adminId uint64) (item *SysAdmin, err error) {
+	err = global.DEFAULT_MYSQL.Model(a).Where("admin_id =  ? ", adminId).First(&item).Error
+	return
+}
+
+type LoginReq struct {
+	AdminName  string `json:"admin_name" binding:"required"` //账号名
+	Password   string `json:"password" binding:"required"`   //密码
+	IsRemember bool   `json:"is_remember"`                   //是否属于受信设备
+}
+
+type LoginResp struct {
+	AdminId   uint64 `json:"admin_id"`   //账号id
+	AdminName string `json:"admin_name"` //账号名
+	RealName  string `json:"real_name"`  //真实姓名
+	Token     string `json:"token"`      //登录token
+	ChangePwd bool   `json:"change_pwd"` //是否需要修改密码
+}
+
+type ModifyPwdReq struct {
+	AdminId    uint64 `json:"admin_id" binding:"required,gte=1"`            //账号ID
+	Pwd        string `json:"pwd" binding:"required"`                       //原密码
+	ConfirmPwd string `json:"confirm_pwd" binding:"required,eqcsfield=Pwd"` //新密码
+}
+
+type ModifyMyPwdReq struct {
+	OldPwd     string `json:"old_pwd" binding:"required"`                      //原密码
+	NewPwd     string `json:"new_pwd" binding:"required"`                      //新密码
+	ConfirmPwd string `json:"confirm_pwd" binding:"required,eqcsfield=NewPwd"` //新密码确认
+}
+
+type ModifyMyInitPwdReq struct {
+	NewPwd     string `json:"new_pwd" binding:"required"`                      //新密码
+	ConfirmPwd string `json:"confirm_pwd" binding:"required,eqcsfield=NewPwd"` //新密码确认
+}
+
+type SysAdminReq struct {
+	AdminId uint64 `json:"admin_id" binding:"required,gte=1"` //账号ID
+}
+
+type SysAdminModifyEnabledReq struct {
+	AdminId uint64 `json:"admin_id" binding:"required,gte=1"` //账号ID
+	Enabled int8   `json:"enabled" binding:"oneof=0 1"`       //1:有效,0:禁用
+}
+
+// 删除
+func (a *SysAdmin) Delete() (err error) {
+	err = global.DEFAULT_MYSQL.Delete(a).Error
+	return
+}
+
+func (a *SysAdmin) GetAdminListByCondition(condition string, pars []interface{}) (list []*SysAdmin, err error) {
+	err = global.DEFAULT_MYSQL.WithContext(context.TODO()).Model(a).
+		Where(condition, pars...).Order("admin_id desc").Find(&list).Error
+	return
+}

+ 139 - 0
models/system/sys_dept.go

@@ -0,0 +1,139 @@
+package system
+
+import (
+	"context"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/base"
+)
+
+// 部门表
+type SysDept struct {
+	DeptId   int64  `gorm:"primaryKey;column:dept_id" json:"dept_id"`
+	ParentId int64  `gorm:"column:parent_id" json:"parent_id"` //父级部门ID
+	DeptName string `gorm:"column:dept_name" json:"dept_name"` //部门名称
+	RootId   int64  `gorm:"column:root_id" json:"root_id"`     //顶层部门ID
+	Sort     int8   `gorm:"column:sort" json:"sort"`           //排序序号
+	Level    int8   `gorm:"column:level" json:"level"`           //当前层级
+	base.TimeBase
+}
+
+// TableName get sql table name.获取数据库表名
+func (d *SysDept) TableName() string {
+	return "sys_dept"
+}
+
+// SysDeptColumns get sql column name.获取数据库列名
+var SysDeptColumns = struct {
+	DeptID     string
+	ParentID   string
+	DeptName   string
+	CreateTime string
+	ModifyTime string
+}{
+	DeptID:     "dept_id",
+	ParentID:   "parent_id",
+	DeptName:   "dept_name",
+	CreateTime: "create_time",
+	ModifyTime: "modify_time",
+}
+
+// SelectPage 分页查询
+func (d *SysDept) SelectPage(page base.IPage, condition string, pars []interface{}) (count int64, results []SysDept, err error) {
+	results = make([]SysDept, 0)
+	query := global.DEFAULT_MYSQL.WithContext(context.TODO()).Model(SysDept{}).Where(condition, pars...)
+	query.Count(&count)
+	if len(page.GetOrderItemsString()) > 0 {
+		query = query.Order(page.GetOrderItemsString())
+	}
+	err = query.Limit(int(page.GetPageSize())).Offset(int(page.Offset())).Find(&results).Error
+	return
+}
+
+func (d *SysDept) GetDeptByName(name string) (list []*SysDept, err error) {
+	err = global.DEFAULT_MYSQL.Model(d).Where("dept_name like ?", name).Find(&list).Error
+	return
+}
+
+
+func (d *SysDept) GetDeptByDeptId(deptId int64) (item *SysDept, err error) {
+	err = global.DEFAULT_MYSQL.Model(d).Where("dept_id = ?", deptId).First(&item).Error
+	return
+}
+
+func (d *SysDept) GetDeptListByCondition(condition string, pars []interface{}, orderStr string) (list []*SysDept, err error) {
+	list = make([]*SysDept, 0)
+	if orderStr == "" {
+		orderStr = "sort asc, create_time desc"
+	}
+	err = global.DEFAULT_MYSQL.WithContext(context.TODO()).Model(d).
+		Where(condition, pars...).Order(orderStr).Find(&list).Error
+	return
+}
+
+type SysDeptAddReq struct {
+	ParentId int64  `json:"parent_id"` // id
+	DeptName string `json:"dept_name" binding:"required"`        // 部门名称
+	Sort     int8   `json:"sort"`
+}
+
+type SysDeptEditReq struct {
+	DeptId   int64  `json:"dept_id" binding:"required,gte=1"`
+	ParentId int64  `json:"parent_id"` // id
+	DeptName string `json:"dept_name" binding:"required"`        // 部门名称
+	Sort     int8   `json:"sort"`
+}
+
+// 新增
+func (d *SysDept) Add() (err error) {
+	err = global.DEFAULT_MYSQL.Create(d).Error
+	return
+}
+
+// 删除
+func (d *SysDept) DeleteByCondition(condition string, pars []interface{}) (err error) {
+	err = global.DEFAULT_MYSQL.Where(condition, pars...).Delete(d).Error
+	return
+}
+
+// 修改
+func (d *SysDept) Update(updateCols []string) (err error) {
+	err = global.DEFAULT_MYSQL.Model(d).Select(updateCols).Updates(d).Error
+	return
+}
+
+type DeptListReq struct {
+	base.PageReq
+	DeptName string `json:"dept_name" form:"dept_name"` //部门名称
+}
+
+type DeptListItemResp struct {
+	DeptId     int64  `json:"dept_id"`
+	ParentId   int64  `json:"parent_id"`   // id
+	DeptName   string `json:"dept_name"`   // 部门名称
+	Sort       int8    `json:"sort"`        //排序
+	CreateTime string `json:"create_time"` //创建时间
+	ModifyTime string `json:"modify_time"` //最后更新时间
+	Children   []*DeptListItemResp
+}
+
+type DelSysDeptReq struct {
+	DeptId   int64  `json:"dept_id" binding:"required,gte=1"`
+}
+
+type DeptAdminListTmpItem struct {
+	AdminId      uint64 `json:"admin_id"`       //账号id
+	RealName     string `json:"real_name"`      //真实姓名
+	DeptId     int64  `json:"dept_id"`
+	ParentId   int64  `json:"parent_id"`   // id
+	DeptName   string `json:"dept_name"`   // 部门名称
+	Sort       int8    `json:"sort"`        //排序
+}
+
+type DeptAdminListItem struct {
+	AdminId  uint64 `json:"admin_id"`  //账号id
+	Name     string `json:"name"`      //账号名
+	DeptId   int64  `json:"dept_id"`
+	ParentId int64  `json:"parent_id"` // id
+	Sort     int8    `json:"sort"`      //排序
+	Children   []*DeptAdminListItem
+}

+ 136 - 0
models/system/sys_menu.go

@@ -0,0 +1,136 @@
+package system
+
+import (
+	"context"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/base"
+)
+
+// 菜单表
+type SysMenu struct {
+	MenuId       int64  `gorm:"primaryKey;column:menu_id;" json:"menu_id"`
+	ParentId     int64  `gorm:"column:parent_id" json:"parent_id"`         //父级菜单ID
+	RootId       int64  `gorm:"column:root_id" json:"root_id"`             //顶层菜单ID
+	Name         string `gorm:"column:name" json:"name"`                   //菜单名称或者按钮名称
+	Sort         int8   `gorm:"column:sort" json:"sort"`                   //排序序号
+	Path         string `gorm:"column:path" json:"path"`                   //路由地址
+	PathName     string `gorm:"column:path_name" json:"path_name"`         //路由名称
+	IconPath     string `gorm:"column:icon_path" json:"icon_path"`         //菜单图标地址
+	Component    string `gorm:"column:component" json:"component"`         //组件路径
+	Hidden       int8   `gorm:"column:hidden" json:"hidden"`               //是否隐藏:1-隐藏 0-显示
+	HiddenLayout int8   `gorm:"column:hidden_layout" json:"hidden_layout"` //是否隐藏layout:1-隐藏 0-显示
+	Level        int8   `gorm:"column:level" json:"level"`
+	MenuType     int8   `gorm:"column:menu_type" json:"menu_type"`     //菜单类型: 0-菜单, 1-按钮
+	ButtonCode   string `gorm:"column:button_code" json:"button_code"` //按钮唯一标识
+	base.TimeBase
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *SysMenu) TableName() string {
+	return "sys_menu"
+}
+type SysMenuAddReq struct {
+	ParentId     int64  `json:"parent_id"`                                     //父级菜单ID
+	Name         string `json:"name" binding:"required"`                       //菜单名称或者按钮名称
+	Sort         int8   `json:"sort"`                                          //排序序号
+	Path         string `json:"path"`                                          //路由地址
+	PathName     string `json:"path_name"`                                     //路由名称
+	IconPath     string `json:"icon_path"`                                     //菜单图标地址
+	Component    string `json:"component"`                                     //组件路径
+	Hidden       int8   `json:"hidden" binding:"oneof=0 1"`                    //是否隐藏:1-隐藏 0-显示
+	HiddenLayout int8   `json:"hidden_layout" binding:"oneof=0 1"`             //是否隐藏layout:1-隐藏 0-显示
+	MenuType     int8   `json:"menu_type" binding:"oneof=0 1"`                 //菜单类型: 0-菜单, 1-按钮
+	ButtonCode   string `json:"button_code" binding:"required_if=menu_type 1"` //按钮唯一标识
+}
+type SysMenuEditReq struct {
+	MenuId     int64  `json:"menu_id" binding:"required,gte=1"`
+	SysMenuAddReq
+}
+// 新增
+func (m *SysMenu) Add() (err error) {
+	err = global.DEFAULT_MYSQL.Create(m).Error
+	return
+}
+
+// 修改
+func (m *SysMenu) Update(updateCols []string) (err error) {
+	err = global.DEFAULT_MYSQL.Model(m).Select(updateCols).Updates(m).Error
+	return
+}
+
+func (m *SysMenu) SelectPage(page base.IPage, condition string, pars []interface{}) (count int64,results []*SysMenu, err error) {
+	results = make([]*SysMenu, 0)
+	query := global.DEFAULT_MYSQL.WithContext(context.TODO()).Model(m).
+		Where(condition, pars...)
+	query.Count(&count)
+	if len(page.GetOrderItemsString()) > 0 {
+		query = query.Order(page.GetOrderItemsString())
+	}
+	err = query.Limit(int(page.GetPageSize())).Offset(int(page.Offset())).Find(&results).Error
+	return
+}
+
+type SysMenuButtonResp struct {
+	MenuId     int64  `json:"menu_id"`                                       //菜单ID
+	ParentId   int64  `json:"parent_id"`                                     //父级菜单ID
+	Name       string `json:"name" binding:"required"`                       //菜单名称或者按钮名称
+	MenuType   int8   `json:"menu_type" binding:"oneof=0 1"`                 //菜单类型: 0-菜单, 1-按钮
+	ButtonCode string `json:"button_code" binding:"required_if=menu_type 1"` //按钮唯一标识
+}
+
+func (m *SysMenu) GetMenuListByCondition(condition string, pars []interface{}, orderStr string) (results []*SysMenu, err error) {
+	results = make([]*SysMenu, 0)
+	if orderStr == "" {
+		orderStr = "sort asc, create_time desc"
+	}
+	err = global.DEFAULT_MYSQL.WithContext(context.TODO()).Model(m).
+		Where(condition, pars...).Order(orderStr).Find(&results).Error
+	return
+}
+
+type MenuShortListItem struct {
+	MenuId int64 `json:"menu_id" form:"menu_id"` //菜单ID
+}
+
+type MenuListReq struct {
+	base.PageReq
+	Name       string `json:"name" form:"name"`        //菜单名称或者按钮名称
+	HideLevel3 int8   `json:"hide_level3" form:"hide_level3"` //返回值是否需要隐藏三级的菜单和三级按钮(1-隐藏,0-不隐藏)
+	HideButton int8   `json:"hide_button" form:"hide_button"` //返回值是否需要隐藏按钮(1-隐藏,0-不隐藏)
+}
+
+type MenuListItemResp struct {
+	MenuId       int64               `json:"menu_id"`       //菜单ID
+	ParentId     int64               `json:"parent_id"`     //父级菜单ID
+	Name         string              `json:"name"`          //菜单名称或者按钮名称
+	Sort         int8                `json:"sort"`          //排序序号
+	Path         string              `json:"path"`          //页面地址或者Api请求地址
+	PathName     string              `json:"path_name"`     //路由名称
+	IconPath     string              `json:"icon_path"`     //菜单图标地址
+	Component    string              `json:"component"`     //组件路径
+	Hidden       int8                `json:"hidden"`        //是否隐藏:1-隐藏 0-显示
+	HiddenLayout int8                `json:"hidden_layout"` //是否隐藏layout:1-隐藏 0-显示
+	Level        int8                `json:"level"`
+	MenuType     int8                `json:"menu_type"`   //菜单类型: 0-菜单, 1-按钮
+	ButtonCode   string              `json:"button_code"` //按钮唯一标识
+	HasBind      bool                `json:"has_bind"`    //是否已绑定,true 已绑定,false 未绑定
+	CreateTime   string              `json:"create_time"` //创建时间
+	ModifyTime   string              `json:"modify_time"` //最后更新时间
+	Children     []*MenuListItemResp `json:"children"`
+}
+
+func (m *SysMenu) GetMenuByName(name string) (list []*SysMenu, err error) {
+	err = global.DEFAULT_MYSQL.Model(m).Where("name like ?", name).Find(&list).Error
+	return
+}
+
+func (m *SysMenu) GetMenuByMenuId(menuId int64) (item *SysMenu, err error) {
+	err = global.DEFAULT_MYSQL.Model(m).Where("menu_id = ?", menuId).First(&item).Error
+	return
+}
+
+// 删除
+func (m *SysMenu) DeleteByCondition(condition string, pars []interface{}) (err error) {
+	err = global.DEFAULT_MYSQL.Where(condition, pars...).Delete(m).Error
+	return
+}

+ 102 - 0
models/system/sys_role.go

@@ -0,0 +1,102 @@
+package system
+
+import (
+	"context"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/base"
+)
+
+// 角色表
+type SysRole struct {
+	RoleId     int64     `gorm:"primaryKey;column:role_id" json:"role_id"`         //序号
+	RoleName   string    `gorm:"column:role_name" json:"role_name"`     //角色名称
+	base.TimeBase
+}
+
+// TableName get sql table name.获取数据库表名
+func (r *SysRole) TableName() string {
+	return "sys_role"
+}
+
+// 新增
+func (r *SysRole) Add() (err error) {
+	err = global.DEFAULT_MYSQL.Create(r).Error
+	return
+}
+
+// 修改
+func (r *SysRole) Update(updateCols []string) (err error) {
+	err = global.DEFAULT_MYSQL.Model(r).Select(updateCols).Updates(r).Error
+	return
+}
+
+type SysRoleAddReq struct {
+	RoleName   string `json:"role_name" binding:"required"` //角色名称
+}
+
+type SysRoleEditReq struct {
+	RoleId     int64  `json:"role_id" binding:"required"`    //序号
+	RoleName   string `json:"role_name" binding:"required"` //角色名称
+}
+
+type SysRoleDelReq struct {
+	RoleId     int64  `json:"role_id" binding:"required"`    //序号
+}
+
+type SysRoleMenuEditReq struct {
+	RoleId     int64  `json:"role_id" binding:"required,gte=1"`    //序号
+	MenuIdList []int64  `json:"menu_id_list" `                //绑定的菜单ID
+}
+
+func (r *SysRole) GetRoleByRoleId(roleId int64) (item *SysRole, err error)  {
+	err = global.DEFAULT_MYSQL.Model(r).Where("role_id = ?", roleId).First(&item).Error
+	return
+}
+
+func (r *SysRole) GetRoleByRoleName(roleName string) (item *SysRole, err error)  {
+	err = global.DEFAULT_MYSQL.Model(r).Where("role_name = ?", roleName).First(&item).Error
+	return
+}
+
+type SysRoleListItem struct {
+	RoleId     int64  `json:"role_id"`     //序号
+	RoleName   string `json:"role_name"`   //角色名称
+	CreateTime string `json:"create_time"` //创建时间
+	ModifyTime string `json:"modify_time"` //最后更新时间
+}
+// SelectPage 分页查询
+func (r *SysRole) SelectPage(page base.IPage, condition string, pars []interface{}) (count int64, results []SysRole, err error) {
+	query := global.DEFAULT_MYSQL.WithContext(context.TODO()).Model(r).Where(condition, pars...)
+	query.Count(&count)
+	if len(page.GetOrderItemsString()) > 0 {
+		query = query.Order(page.GetOrderItemsString())
+	}
+	err = query.Limit(int(page.GetPageSize())).Offset(int(page.Offset())).Find(&results).Error
+	return
+}
+
+func (r *SysRole) GetRoleNameList() (list []*SysRole, err error)  {
+	err = global.DEFAULT_MYSQL.Model(r).Find(&list).Error
+	return
+}
+
+
+type SysRoleListReq struct {
+	RoleName  string  `json:"role_name" form:"role_name"`    //序号
+	base.PageReq
+}
+
+type SysRoleMenuListReq struct {
+	RoleId     int64  `json:"role_id" form:"role_id"`    //序号
+}
+
+type SysRoleMenuResp struct {
+	List       []*MenuListItemResp `json:"list"`
+	ChoiceList []int64    `json:"choice_list"`
+}
+
+// 删除
+func (r *SysRole) Delete() (err error) {
+	err = global.DEFAULT_MYSQL.Delete(r).Error
+	return
+}

+ 42 - 0
models/system/sys_role_menu.go

@@ -0,0 +1,42 @@
+package system
+
+import (
+	"context"
+	"hongze/hrms_api/global"
+	"time"
+)
+
+// 角色和菜单关联表
+type SysRoleMenu struct {
+	RoleMenuId int64       `gorm:"primaryKey;column:role_menu_id" json:"_"` //序号
+	RoleId     int64       `gorm:"column:role_id" json:"role_id"`           //角色ID
+	MenuId     int64       `gorm:"column:menu_id" json:"menu_id"`           //菜单ID
+	CreateTime time.Time `gorm:"autoCreateTime;column:create_time" json:"create_time"` //创建时间
+	ModifyTime time.Time `gorm:"autoUpdateTime:milli;column:modify_time" json:"modify_time"` //最后更新时间
+}
+
+// TableName get sql table name.获取数据库表名
+func (rm *SysRoleMenu) TableName() string {
+	return "sys_role_menu"
+}
+
+// 批量新增
+func (rm *SysRoleMenu) AddMulti(list []*SysRoleMenu) (err error) {
+	err = global.DEFAULT_MYSQL.CreateInBatches(list, len(list)).Error
+	return
+}
+
+// 删除
+func (rm *SysRoleMenu) DeleteByCondition(condition string, pars []interface{}) (err error) {
+	err = global.DEFAULT_MYSQL.Where(condition, pars...).Delete(rm).Error
+	return
+}
+
+func (rm *SysRoleMenu) GetMenuListByCondition(condition string, pars []interface{}) (results []*SysRoleMenu, err error) {
+	err = global.DEFAULT_MYSQL.WithContext(context.TODO()).Model(rm).
+		Table("sys_role_menu as rm ").
+		Select("rm.*").
+		Joins("Inner JOIN sys_menu r ON rm.menu_id = r.menu_id").
+		Where(condition, pars...).Find(&results).Error
+	return
+}

+ 38 - 0
models/system/sys_session.go

@@ -0,0 +1,38 @@
+package system
+
+import (
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/base"
+	"time"
+)
+
+type SysSession struct {
+	SessionId   int64     `gorm:"primaryKey;column:session_id" json:"_"`
+	AdminId     uint64     `gorm:"column:admin_id" json:"admin_id"`         //账号ID
+	OpenId      string    `gorm:"column:open_id" json:"open_id"`           //openid
+	AccessToken string    `gorm:"column:access_token" json:"access_token"` //登录token
+	ExpireTime  time.Time `gorm:"column:expire_time" json:"expire_time"`   //超时时间
+	base.TimeBase
+}
+
+type LoginTokenContent struct {
+	AdminId     uint64
+	Password 	string
+	IsRemember      bool `description:"是否属于受信设备"`
+}
+// TableName get sql table name.获取数据库表名
+func (s *SysSession) TableName() string {
+	return "sys_session"
+}
+
+// Add 新增
+func (s *SysSession) Add() (err error) {
+	err = global.DEFAULT_MYSQL.Create(s).Error
+	return
+}
+
+func (s *SysSession) GetSessionByToken(token string) (item *SysSession, err error) {
+	err = global.DEFAULT_MYSQL.Model(s).Where("access_token = ? ", token).Order("session_id DESC").First(&item).Error
+	return
+}
+

+ 13 - 0
routers/auth.go

@@ -0,0 +1,13 @@
+package routers
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hrms_api/controller"
+)
+
+func InitAuth(baseGroup *gin.RouterGroup) {
+	//登录
+	authController := new(controller.AuthController)
+	authGroup := baseGroup.Group("auth/")
+	authGroup.POST("login", authController.Login)
+}

+ 12 - 0
routers/resource.go

@@ -0,0 +1,12 @@
+package routers
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hrms_api/controller/resource"
+)
+
+func InitResource(baseGroup *gin.RouterGroup) {
+	videoController := new(resource.VideoController)
+	videoGroup := baseGroup.Group("video/")
+	videoGroup.GET("oss_sts_token", videoController.GetOssStsToken)
+}

+ 49 - 0
routers/system.go

@@ -0,0 +1,49 @@
+package routers
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hrms_api/controller/system"
+	"hongze/hrms_api/middleware"
+)
+
+func InitSystem(systemGroup *gin.RouterGroup) {
+	//角色
+	roleController := new(system.SysRoleController)
+	roleGroup := systemGroup.Group("role/").Use(middleware.Token())
+	roleGroup.GET("list", roleController.List)
+	roleGroup.GET("menu/list", roleController.MenuList)
+	roleGroup.POST("add", roleController.Add)
+	roleGroup.PUT("menu/edit", roleController.EditMenu)
+	roleGroup.DELETE("delete", roleController.Delete)
+
+	//部门
+	deptController := new(system.SysDeptController)
+	deptGroup := systemGroup.Group("dept/").Use(middleware.Token())
+	deptGroup.GET("list", deptController.List)
+	deptGroup.POST("add", deptController.Add)
+	deptGroup.PUT("edit", deptController.Edit)
+	deptGroup.DELETE("delete", deptController.Delete)
+	deptGroup.GET("admin", deptController.AdminList)
+
+	//菜单
+	menuController := new(system.SysMenuController)
+	menuGroup := systemGroup.Group("menu/").Use(middleware.Token())
+	menuGroup.POST("add", menuController.Add)
+	menuGroup.POST("edit", menuController.Edit)
+	menuGroup.GET("list", menuController.List)
+	menuGroup.GET("buttons", menuController.ButtonList)
+	menuGroup.GET("all_list", menuController.AllList)
+	menuGroup.DELETE("delete", menuController.Delete)
+
+	//管理员账号
+	adminController := new(system.SysAdminController)
+	adminGroup := systemGroup.Group("admin/").Use(middleware.Token())
+	adminGroup.POST("add", adminController.Add)
+	adminGroup.POST("del", adminController.Delete)
+	adminGroup.POST("edit", adminController.Edit)
+	adminGroup.GET("list", adminController.List)
+	adminGroup.POST("modify/pwd", adminController.ModifyPwd)
+	adminGroup.POST("modify/my/init_pwd", adminController.ModifyMyInitPwd)
+	adminGroup.POST("modify/my/pwd", adminController.ModifyMyPwd)
+	adminGroup.POST("modify/enabled", adminController.ModifyEnabled)
+}

+ 30 - 0
services/alarm_msg/alarm_msg.go

@@ -0,0 +1,30 @@
+package alarm_msg
+
+import (
+	"encoding/json"
+	"github.com/rdlucklib/rdluck_tools/http"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/utils"
+)
+
+var (
+	AlarmMsgUrl = "http://127.0.0.1:8606/api/alarm/send"
+)
+
+//projectName-项目名称
+//runMode-运行模式
+//msgBody-消息内容
+//level:消息基本,1:提示消息,2:警告消息,3:严重错误信息,默认为1 提示消息
+func SendAlarmMsg(msgBody string, level int) {
+	params := make(map[string]interface{})
+	params["ProjectName"] = utils.APPNAME
+	params["RunMode"] = global.CONFIG.Serve.RunMode
+	params["MsgBody"] = msgBody
+	params["Level"] = level
+	param, err := json.Marshal(params)
+	if err != nil {
+		global.LOG.Critical("SendAlarmMsg json.Marshal Err:" + err.Error())
+		return
+	}
+	http.Post(AlarmMsgUrl, string(param))
+}

+ 446 - 0
services/resource/email.go

@@ -0,0 +1,446 @@
+package resource
+
+import (
+	"errors"
+	"github.com/emersion/go-imap"
+	"github.com/emersion/go-imap/client"
+	"github.com/emersion/go-message/charset"
+	"github.com/emersion/go-message/mail"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/utils"
+	"io"
+	"io/ioutil"
+	"os"
+	"regexp"
+	"strings"
+	"time"
+)
+
+type EmailImapService struct {
+	Client *client.Client
+}
+
+func NewEmailImapService() *EmailImapService {
+	global.LOG.Info("Connecting to server...")
+
+	// Connect to server
+	//c, err := client.DialTLS("imap.qq.com:993", nil)
+	var emailHost string
+	if global.CONFIG.Serve.RunMode != "debug" {
+		emailHost = "imap.qiye.aliyun.com:993"
+	} else {
+		//测试邮箱
+		emailHost = "imap.aliyun.com:993"
+	}
+	c, err := client.DialTLS(emailHost, nil)
+	if err != nil {
+		panic("连接邮箱服务器失败:" + err.Error())
+	}
+	global.LOG.Info("Connected success")
+	return &EmailImapService{Client: c}
+}
+
+type AttachmentListItem struct {
+	Title      string
+	Date       time.Time
+	Name       string
+	Position   string
+	FullPath   string
+	FileName   string
+	FromEmails []string
+	LoadUrl    string
+}
+
+//DownLoadCv 下载简历附件接口
+func (e *EmailImapService) DownLoadEmailCv(username, password string, attachmentChan chan *AttachmentListItem) (err error) {
+	// Don't forget to logout
+	defer e.Client.Logout()
+	defer close(attachmentChan)
+
+	// Login
+	if err = e.Client.Login(username, password); err != nil {
+		global.LOG.Info("login failed err: " + err.Error())
+		return
+	}
+
+	global.LOG.Info("login in")
+	// Select INBOX
+	mbox, err := e.Client.Select("INBOX", false)
+	if err != nil {
+		global.LOG.Error(err)
+		return
+	}
+
+	// Get the last message
+	if mbox.Messages == 0 {
+		global.LOG.Error("No message in mailbox")
+		return
+	}
+	/*// 筛选最近7天的邮件
+	//criteria := imap.NewSearchCriteria()
+	//criteria.WithoutFlags = []string{imap.SeenFlag}
+	//criteria.SentSince = time.Date(1984, 11, 5, 0, 0, 0, 0, time.UTC)
+	//ids, err := e.Client.Search(criteria)
+
+	if err != nil {
+		global.LOG.Error("No search message in mailbox"+err.Error())
+		return
+	}
+	if len(ids) == 0 {
+		global.LOG.Error("No search message in mailbox")
+		return
+	}
+	seqSet := new(imap.SeqSet)
+	seqSet.AddNum(ids...)*/
+	seqSet := new(imap.SeqSet)
+	from := uint32(1)
+	to := mbox.Messages
+	if mbox.Messages > 100 {
+		// We're using unsigned integers here, only subtract if the result is > 0
+		from = mbox.Messages - 100
+	}
+	seqSet.AddRange(from, to)
+	global.LOG.Infof("mbox.Messages length :%d", mbox.Messages)
+
+	// Get the whole message body
+	var section imap.BodySectionName
+	items := []imap.FetchItem{section.FetchItem()}
+
+	messages := make(chan *imap.Message, mbox.Messages)
+	done := make(chan error, 1)
+	go func() {
+		done <- e.Client.Fetch(seqSet, items, messages)
+	}()
+	global.LOG.Info("最近7天收到的邮件:")
+	imap.CharsetReader = charset.Reader
+	beforeDate7 := time.Now().AddDate(0, 0, -7)
+	for msg := range messages {
+		if msg == nil {
+			err = errors.New("Server didn't returned message")
+			global.LOG.Error("Server didn't returned message")
+			return
+		}
+		tmp := new(AttachmentListItem)
+		section = imap.BodySectionName{}
+		r := msg.GetBody(&section)
+		if r == nil {
+			err = errors.New("Server didn't returned message body")
+			global.LOG.Error("Server didn't returned message body")
+			return
+		}
+		var mr *mail.Reader
+		mr, err = mail.CreateReader(r)
+		if err != nil {
+			global.LOG.Error(err)
+			return
+		}
+
+		// Print some info about the message
+		header := mr.Header
+		var emailDate time.Time
+		if emailDate, err = header.Date(); err == nil {
+			global.LOG.Infof("Date:%s", emailDate.Format(utils.FormatDateTime))
+			if emailDate.Before(beforeDate7) {
+				continue
+			}
+			//global.LOG.Infof("Date:", emailDate)
+			tmp.Date = emailDate
+		}
+		if subject, err := header.Subject(); err == nil {
+			global.LOG.Infof("Subject:%s", subject)
+			newSubject, info, flag := DealSubject(subject)
+			if !flag {
+				continue
+			}
+			tmp.Title = newSubject
+			tmp.Name = info.Name
+			tmp.Position = info.Position
+		}
+		if from, err := header.AddressList("From"); err == nil {
+			global.LOG.Infof("From:%s", from)
+			//tmp.SenderEmails = from
+			for _, fa := range from {
+				tmp.FromEmails = append(tmp.FromEmails, fa.Address)
+			}
+		}
+		if to, err := header.AddressList("To"); err == nil {
+			global.LOG.Infof("To:%s", to)
+		}
+
+		// Process each message's part
+		for {
+			p, tErr := mr.NextPart()
+			if tErr == io.EOF {
+				break
+			} else if tErr != nil {
+				global.LOG.Errorf("Process each message's part err:%v", tErr)
+				return
+			}
+
+			switch h := p.Header.(type) {
+			case *mail.InlineHeader:
+				// This is the message's text (can be plain-text or HTML)
+				_, _ = ioutil.ReadAll(p.Body)
+				//global.LOG.Infof("Got text: %v", string(b))
+			case *mail.AttachmentHeader:
+				// This is an attachment
+				filename, _ := h.Filename()
+				tmp.FileName = filename
+				global.LOG.Infof("Got attachment: %v", filename)
+				//保存到本地
+				dir, tmpErr := mk_dir("download/cv/" + emailDate.Format(utils.FormatDateTimeUnSpace))
+				if tmpErr != nil {
+					err = tmpErr
+					global.LOG.Infof("%v 写入失败 err %s", filename, err.Error())
+					return
+				}
+				filename = dir + "/" + filename
+				content, _ := ioutil.ReadAll(p.Body)
+				//global.LOG.Infof("Got text: %v", string(content))
+				err = write_to_file(filename, content)
+				if err != nil {
+					global.LOG.Infof("%v 写入失败", filename)
+					return
+				}
+				tmp.FullPath = filename
+
+				/*tmpFileName := strings.Split(path.Base(tmp.FileName), ".")
+				tmpName := tmpFileName[0]
+				ext := tmpFileName[1]
+				global.LOG.Infof("Got attachment ext: %v", ext)
+				if ext == "docx" {
+					fileOutPath, e := FuncDocs2Pdf(global.CONFIG.Serve.LibreOfficePath, filename ,dir,"pdf")
+					if e != nil {
+						err = e
+						global.LOG.Infof("docx 转pdf 失败:%v",e.Error())
+						return
+					}
+					tmp.FullPath = fileOutPath
+					tmp.FileName = tmpName + ".pdf"
+				}*/
+				//把结果放到channel中
+				attachmentChan <- tmp
+			}
+		}
+	}
+
+	if err = <-done; err != nil {
+		global.LOG.Info("email client Fetch Err: " + err.Error())
+		return
+	}
+	return
+}
+
+func mk_dir(dir_path string) (string, error) {
+	var path string
+	if os.IsPathSeparator('\\') { //前边的判断是否是系统的分隔符
+		path = "\\"
+	} else {
+		path = "/"
+	}
+	//fmt.Println(path)
+	dir, _ := os.Getwd()                               //当前的目录
+	err := os.MkdirAll(dir+path+dir_path, os.ModePerm) //在当前目录下生成md目录
+	if err != nil {
+		global.LOG.Infof("%v", err)
+		return "", nil
+	}
+	return dir + path + dir_path, nil
+}
+
+/*
+	函数名称:write_to_file
+	函数作用:写内容到文件
+	输入参数:filename(文件名),content(内容)
+	输出参数:无
+*/
+
+func write_to_file(filename string, content []byte) (err error) {
+	var fileHandle *os.File
+	/*if !checkFileIsExist(filename) { //如果文件存在
+		fileHandle, err = os.Create(filename) //创建文件
+		global.LOG.Infof("文件不存在")
+		if err != nil {
+			global.LOG.Infof("Err" + err.Error())
+		}
+	}else{*/
+	fileHandle, err = os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
+	//global.LOG.Infof("文件存在")
+	//}
+	if err != nil {
+		return
+	}
+
+	defer fileHandle.Close()
+	//循环读取
+
+	// NewWriter 默认缓冲区大小是 4096
+	// 需要使用自定义缓冲区的writer 使用 NewWriterSize()方法
+	//	buf := bufio.NewWriter(fileHandle)
+	// 字节写入
+	// 字符串写入
+	_, err = fileHandle.WriteString(string(content))
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+func DealSubject(subject string) (newSubject string, person AttachmentListItem, flag bool) {
+	if strings.Contains(subject, "转发:") || strings.Contains(subject, "Fwd:") {
+		reg := `^[Fwd:|转发:]+`
+		re := regexp.MustCompile(reg)
+		subject = re.ReplaceAllString(subject, "")
+	}
+	newSubject = subject
+	person, flag = dealBossSubject(subject)
+	if !flag {
+		person, flag = dealDefaultSubject(subject)
+		if !flag {
+			person, flag = dealLiePinSubject(subject)
+			if !flag {
+				person, flag = dealOtherSubject(subject)
+			}
+		}
+	}
+	return
+}
+
+func dealBossSubject(subject string) (person AttachmentListItem, flag bool) {
+	matched, err := regexp.MatchString(`(.*)【BOSS直聘】`, subject)
+	if err != nil {
+		return
+	}
+	if !matched {
+		return
+	}
+	items := strings.Split(subject, "|")
+	if len(items) < 3 {
+		return
+	}
+	name := items[0]
+	global.LOG.Infof("求职姓名:%s", name)
+	//获取职位
+	items1 := strings.Split(items[1], "应聘")
+	if len(items1) < 2 {
+		return
+	}
+	position := strings.Trim(items1[1], " ")
+	global.LOG.Infof("求职岗位:%s", position)
+	person.Name = name
+	person.Position = position
+	flag = true
+	return
+}
+
+func dealDefaultSubject(subject string) (person AttachmentListItem, flag bool) {
+	reg := `[(](.*)[)-](.*){1,10}[先生|女士]$`
+	matched, err := regexp.MatchString(reg, subject)
+	if err != nil {
+		return
+	}
+	if !matched {
+		return
+	}
+	re := regexp.MustCompile(reg)
+	position := re.ReplaceAllString(subject, "")
+	global.LOG.Infof("求职岗位:%s", position)
+	if position == "" {
+		return
+	}
+	//获取职位
+	items := strings.Split(subject, "-")
+	if len(items) < 2 {
+		return
+	}
+
+	name := items[len(items)-1]
+	global.LOG.Infof("求职姓名:%s", name)
+	person.Name = name
+	person.Position = position
+	flag = true
+	return
+}
+
+func dealLiePinSubject(subject string) (person AttachmentListItem, flag bool) {
+	reg := `来自猎聘的候选人`
+	matched, err := regexp.MatchString(reg, subject)
+	if err != nil {
+		return
+	}
+	if !matched {
+		return
+	}
+	// Regex pattern captures "key: value" pair from the content.
+	pattern := regexp.MustCompile("【(?P<key1>(.*)+)_(?P<key2>(.*)+)】(?P<key3>(.*)+)_(?P<key4>(.*)+)")
+
+	// Template to convert "key: value" to "key=value" by
+	// referencing the values captured by the regex pattern.
+	template := "$key1=$key2=$key3=$key4\n"
+
+	result := []byte{}
+
+	// For each match of the regex in the content.
+	for _, submatches := range pattern.FindAllStringSubmatchIndex(subject, -1) {
+		// Apply the captured submatches to the template and append the output
+		// to the result.
+		result = pattern.ExpandString(result, template, subject, submatches)
+	}
+	global.LOG.Infof("求职邮件匹配结果:%s", string(result))
+	//获取职位
+	tmp := strings.Split(string(result), "=")
+	if len(tmp) < 4 {
+		return
+	}
+
+	position := strings.Trim(tmp[0], " ")
+	global.LOG.Infof("求职岗位:%s", position)
+	name := strings.Trim(tmp[2], " ")
+	global.LOG.Infof("求职姓名:%s", name)
+	person.Name = name
+	person.Position = position
+	flag = true
+	return
+}
+
+func dealOtherSubject(subject string) (person AttachmentListItem, flag bool) {
+	reg := `^New application`
+	matched, err := regexp.MatchString(reg, subject)
+	if err != nil {
+		return
+	}
+	if !matched {
+		return
+	}
+	// Regex pattern captures "key: value" pair from the content.
+	pattern := regexp.MustCompile(`(?P<key1>[a-zA-z\s]+)from(?P<key2>[a-zA-z\s]+)`)
+
+	// Template to convert "key: value" to "key=value" by
+	// referencing the values captured by the regex pattern.
+	template := "$key1=$key2\n"
+
+	result := []byte{}
+
+	// For each match of the regex in the content.
+	for _, submatches := range pattern.FindAllStringSubmatchIndex(subject, -1) {
+		// Apply the captured submatches to the template and append the output
+		// to the result.
+		result = pattern.ExpandString(result, template, subject, submatches)
+	}
+	global.LOG.Infof("求职邮件匹配结果:%s", string(result))
+	//获取职位
+	tmp := strings.Split(string(result), "=")
+	if len(tmp) < 2 {
+		return
+	}
+
+	position := strings.Trim(tmp[0], " ")
+	global.LOG.Infof("求职岗位:%s", position)
+	name := strings.Trim(tmp[1], " ")
+	global.LOG.Infof("求职姓名:%s", name)
+	person.Name = name
+	person.Position = position
+	flag = true
+	return
+}

+ 264 - 0
services/resource/oss.go

@@ -0,0 +1,264 @@
+package resource
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/services/alarm_msg"
+	"hongze/hrms_api/utils"
+	"os"
+	"time"
+)
+
+var (
+	RoleArn            = "acs:ram::1884217364581072:role/hzossrole"
+	RoleSessionName    = "hzossRole"
+	RAMAccessKeyId     = "LTAI5t9S36LXduhTnECVY6Hn"
+	RAMAccessKeySecret = "V6FG5bdzjKKqMpqEqxeWijJCdzDCuL"
+	STSTokenCacheKey   = "hrms_api:oss:sts_token"
+)
+
+// UploadAliyun 图片上传到阿里云
+func UploadAliyun(filename, filepath string) (string, error) {
+	client, err := oss.New(global.CONFIG.AliOss.EndPoint, global.CONFIG.AliOss.AccessKeyId, global.CONFIG.AliOss.AccessKeySecret)
+	if err != nil {
+		return "1", err
+	}
+	bucket, err := client.Bucket(global.CONFIG.AliOss.BucketName)
+	if err != nil {
+		return "2", err
+	}
+	path := global.CONFIG.AliOss.UploadDir + time.Now().Format("200601/20060102/")
+	path += filename
+	err = bucket.PutObjectFromFile(path, filepath)
+	if err != nil {
+		return "3", err
+	}
+	path = global.CONFIG.AliOss.ImgHost + path
+	return path, err
+}
+
+// UploadAudioAliyun 音频上传到阿里云
+func UploadAudioAliyun(filename, filepath string) (string, error) {
+	client, err := oss.New(global.CONFIG.AliOss.EndPoint, global.CONFIG.AliOss.AccessKeyId, global.CONFIG.AliOss.AccessKeySecret)
+	if err != nil {
+		return "1", err
+	}
+	bucket, err := client.Bucket(global.CONFIG.AliOss.BucketName)
+	if err != nil {
+		return "2", err
+	}
+	path := global.CONFIG.AliOss.UploadAudioDir + time.Now().Format("200601/20060102/")
+	path += filename
+	err = bucket.PutObjectFromFile(path, filepath)
+	if err != nil {
+		return "3", err
+	}
+	path = global.CONFIG.AliOss.ImgHost + path
+	return path, err
+}
+
+// UploadVideoAliyun 视频上传到阿里云
+func UploadVideoAliyun(filename, filepath, savePath string) error {
+	defer func() {
+		_ = os.Remove(filepath)
+	}()
+	client, err := oss.New(global.CONFIG.AliOss.EndPoint, global.CONFIG.AliOss.AccessKeyId, global.CONFIG.AliOss.AccessKeySecret)
+	if err != nil {
+		return err
+	}
+	bucket, err := client.Bucket(global.CONFIG.AliOss.BucketName)
+	if err != nil {
+		return err
+	}
+	//path := global.CONFIG.AliOss.UploadAudioDir + time.Now().Format("200601/20060102/")
+	//path += filename
+	err = bucket.PutObjectFromFile(savePath, filepath)
+	if err != nil {
+		return err
+	}
+	//path = global.CONFIG.AliOss.ImgHost + path
+	//return path,err
+	return err
+}
+
+var (
+	Bucketname      = "hzchart"
+	Endpoint        = "oss-cn-shanghai.aliyuncs.com"
+	ResourceHost    = "https://hzstatic.hzinsights.com/"
+	StaticDir       = "static/"
+	AccessKeyId     = "LTAIFMZYQhS2BTvW"
+	AccessKeySecret = "12kk1ptCHoGWedhBnKRVW5hRJzq9Fq"
+	UploadDir       = "static/images/"
+)
+
+// UploadAliyunToDir
+func UploadAliyunToDir(filename, filepath, fileDir string) (string, error) {
+	client, err := oss.New(Endpoint, AccessKeyId, AccessKeySecret)
+	if err != nil {
+		return "1", err
+	}
+	bucket, err := client.Bucket(Bucketname)
+	if err != nil {
+		return "2", err
+	}
+	if fileDir == "" {
+		fileDir = time.Now().Format("200601/20060102/")
+	}
+	path := StaticDir + fileDir
+	path += filename
+	err = bucket.PutObjectFromFile(path, filepath)
+	if err != nil {
+		return "3", err
+	}
+	path = ResourceHost + path
+	return path, err
+}
+
+// UploadAliyunV2 图片上传到阿里云
+func UploadAliyunV2(filename, filepath string) (string, error) {
+	client, err := oss.New(Endpoint, AccessKeyId, AccessKeySecret)
+	if err != nil {
+		return "1", err
+	}
+	bucket, err := client.Bucket(Bucketname)
+	if err != nil {
+		return "2", err
+	}
+	path := UploadDir + time.Now().Format("200601/20060102/")
+	path += filename
+	err = bucket.PutObjectFromFile(path, filepath)
+	if err != nil {
+		return "3", err
+	}
+	path = ResourceHost + path
+	return path, err
+}
+
+const (
+	HzEndpoint = "oss-cn-shanghai.aliyuncs.com"
+	// todo 修改bucket空间
+	HzBucketName string = "hzchart"
+)
+
+//上传文件到阿里云
+func UploadFileToAliyun(filename, filepath, savePath string) error {
+	defer func() {
+		os.Remove(filepath)
+	}()
+	client, err := oss.New(HzEndpoint, AccessKeyId, AccessKeySecret)
+	if err != nil {
+		return err
+	}
+	bucket, err := client.Bucket(HzBucketName)
+	if err != nil {
+		return err
+	}
+	//path := utils.Upload_Audio_Dir + time.Now().Format("200601/20060102/")
+	//path += filename
+	err = bucket.PutObjectFromFile(savePath, filepath)
+	if err != nil {
+		return err
+	}
+	return err
+}
+
+type STSToken struct {
+	AccessKeyId     string `json:"access_key_id"`
+	AccessKeySecret string `json:"access_key_secret"`
+	SecurityToken   string `json:"security_token"`
+	ExpiredTime     string `json:"expired_time"`
+}
+
+// GetOssSTSToken 获取STSToken
+func GetOssSTSToken() (item *STSToken, err error) {
+	defer func() {
+		if err != nil {
+			global.LOG.Info("GetOssSTSToken Err:" + err.Error())
+			go alarm_msg.SendAlarmMsg("获取STSToken失败, ErrMsg: "+err.Error(), 3)
+		}
+	}()
+	item = new(STSToken)
+	// 获取缓存中的Token
+	recent, _ := global.Redis.Get(context.TODO(), STSTokenCacheKey).Result()
+	if recent != "" {
+		lastToken := new(STSToken)
+		if e := json.Unmarshal([]byte(recent), &lastToken); e != nil {
+			err = errors.New("GetOssSTSToken lastToken Unmarshal Err: " + e.Error())
+			return
+		}
+		// 未防止正在上传大文件时Token过期, 将判定的过期时间提前10分钟
+		afterTime := time.Now().Local().Add(10 * time.Minute)
+		expired, e := time.ParseInLocation(utils.FormatDateTime, lastToken.ExpiredTime, time.Local)
+		if e != nil {
+			err = errors.New("GetOssSTSToken expiredTime Parse Err: " + e.Error())
+			return
+		}
+		if expired.After(afterTime) {
+			item.AccessKeyId = lastToken.AccessKeyId
+			item.AccessKeySecret = lastToken.AccessKeySecret
+			item.SecurityToken = lastToken.SecurityToken
+			item.ExpiredTime = lastToken.ExpiredTime
+			return
+		}
+	}
+	// 已过期则获取新的token
+	newToken, e := NewSTSToken()
+	if e != nil {
+		err = errors.New("GetOssSTSToken NewSTSToken Err: " + e.Error())
+		return
+	}
+	newTokenJson, e := json.Marshal(newToken)
+	if e != nil {
+		err = errors.New("GetOssSTSToken NewToken JSON Err: " + e.Error())
+		return
+	}
+	// 覆盖缓存
+
+	if statusCmd := global.Redis.SetEX(context.TODO(), STSTokenCacheKey, newTokenJson, time.Hour); statusCmd.Err() != nil {
+		err = errors.New("GetOssSTSToken SetRedis Err: " + statusCmd.Err().Error())
+		return
+	}
+	item = newToken
+	return
+}
+
+// NewSTSToken 获取一个新的STSToken
+func NewSTSToken() (item *STSToken, err error) {
+	defer func() {
+		if err != nil {
+			global.LOG.Info("NewSTSToken Err:" + err.Error())
+		}
+	}()
+	item = new(STSToken)
+	client, e := sts.NewClientWithAccessKey("cn-shanghai", RAMAccessKeyId, RAMAccessKeySecret)
+	if e != nil {
+		err = errors.New("NewSTSToken NewClient Err: " + e.Error())
+		return
+	}
+	request := sts.CreateAssumeRoleRequest()
+	request.Scheme = "https"
+	request.RegionId = "cn-shanghai"
+	request.RoleArn = RoleArn
+	now := time.Now().Format(utils.FormatDateTimeUnSpace)
+	request.RoleSessionName = RoleSessionName + now
+	request.DurationSeconds = "3600"
+
+	response, e := client.AssumeRole(request)
+	if e != nil {
+		err = errors.New("NewSTSToken AssumeRole Err: " + e.Error())
+		return
+	}
+	if response != nil {
+		item.AccessKeyId = response.Credentials.AccessKeyId
+		item.AccessKeySecret = response.Credentials.AccessKeySecret
+		item.SecurityToken = response.Credentials.SecurityToken
+		t, _ := time.Parse(time.RFC3339, response.Credentials.Expiration)
+		expiration := t.In(time.Local)
+		item.ExpiredTime = expiration.Format(utils.FormatDateTime)
+	}
+	return
+}

+ 69 - 0
services/resource/pdf.go

@@ -0,0 +1,69 @@
+package resource
+
+import (
+	"hongze/hrms_api/global"
+	"os"
+	"os/exec"
+	"path"
+	"strings"
+)
+
+//FuncDocs2Pdf
+/**
+*@tips libreoffice 转换指令:
+* libreoffice6.2 invisible --convert-to pdf csDoc.doc --outdir /home/[转出目录]
+*
+* @function 实现文档类型转换为pdf或html
+* @param command:libreofficed的命令(具体以版本为准);win:soffice; linux:libreoffice6.2
+*     fileSrcPath:转换文件的路径
+*         fileOutDir:转换后文件存储目录
+*       converterType:转换的类型pdf/html
+* @return fileOutPath 转换成功生成的文件的路径 error 转换错误
+ */
+func FuncDocs2Pdf(command string, fileSrcPath string, fileOutDir string, converterType string) (fileOutPath string, error error) {
+	//校验fileSrcPath
+	srcFile, erByOpenSrcFile := os.Open(fileSrcPath)
+	if erByOpenSrcFile != nil && os.IsNotExist(erByOpenSrcFile) {
+		return "", erByOpenSrcFile
+	}
+	//如文件输出目录fileOutDir不存在则自动创建
+	outFileDir, erByOpenFileOutDir := os.Open(fileOutDir)
+	if erByOpenFileOutDir != nil && os.IsNotExist(erByOpenFileOutDir) {
+		erByCreateFileOutDir := os.MkdirAll(fileOutDir, os.ModePerm)
+		if erByCreateFileOutDir != nil {
+			global.LOG.Info("File ouput dir create error.....", erByCreateFileOutDir.Error())
+			return "", erByCreateFileOutDir
+		}
+	}
+	//关闭流
+	defer func() {
+		_ = srcFile.Close()
+		_ = outFileDir.Close()
+	}()
+	//convert
+	cmd := exec.Command(command, "--invisible", "--convert-to", converterType,
+		fileSrcPath, "--outdir", fileOutDir)
+	_, errByCmdStart := cmd.Output()
+	//命令调用转换失败
+	if errByCmdStart != nil {
+		return "", errByCmdStart
+	}
+	//success
+	fileOutPath = fileOutDir + "/" + strings.Split(path.Base(fileSrcPath), ".")[0]
+	if converterType == "html" {
+		fileOutPath += ".html"
+	} else {
+		fileOutPath += ".pdf"
+	}
+
+	//校验fileSrcPath
+	outFile, erByOpenOutFile := os.Open(fileOutPath)
+	if erByOpenOutFile != nil {
+		return "", erByOpenOutFile
+	}
+	defer func() {
+		_ = outFile.Close()
+	}()
+	//fmt.Println("文件转换成功...", string(byteByStat))
+	return fileOutPath, nil
+}

+ 87 - 0
services/system/sys_admin.go

@@ -0,0 +1,87 @@
+package system
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/models/system"
+	"hongze/hrms_api/utils"
+	"strconv"
+	"time"
+)
+
+func Login(adminName, password string, isRemember bool) (ret system.LoginResp, err error, errMsg string) {
+	//查询管理员账号是否存在
+	admin := new(system.SysAdmin)
+	adminInfo, err := admin.GetAdminByAdminName(adminName)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			errMsg = "登录失败,账号错误"
+			err = errors.New(errMsg + "Err:" + err.Error())
+			return
+		}
+		errMsg = "登录失败,查询账号出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	//查询密码是否匹配
+	if adminInfo.Password != password {
+		errMsg = "登录失败,密码错误"
+		err = errors.New(errMsg)
+		return
+	}
+	//查询账号是否有效
+	if adminInfo.Enabled == 0 {
+		errMsg = "您的账号已被禁用,如需登录,请联系管理员"
+		err = errors.New(errMsg + "已禁用账号:" + adminInfo.AdminName + " " + adminInfo.RealName)
+		return
+	}
+	//生成token
+	token, err := utils.GenToken(strconv.Itoa(int(adminInfo.AdminId)) + adminInfo.AdminName)
+	if err != nil {
+		errMsg = "登录失败,生成token出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	//新增session记录
+	sysSession := new(system.LoginTokenContent)
+	sysSession.AdminId = adminInfo.AdminId
+	sysSession.Password = adminInfo.Password
+	sysSession.IsRemember = isRemember
+
+	tokenStr, _ := json.Marshal(sysSession)
+	//将session保存到redis当中
+	global.Redis.SetEX(context.TODO(), utils.SYSTEM_LOGIN_TOKEN+token, tokenStr, 120*time.Minute)
+
+	// 获取不可信的登录态,并将该登录态重置掉,不允许多次登录
+	noTrustLoginKey := fmt.Sprint(utils.SYSTEM_LOGIN_TOKEN_NO_TRUST, adminInfo.AdminId)
+	noTrustLoginToken, _ := global.Redis.Get(context.TODO(), noTrustLoginKey).Result()
+	if noTrustLoginToken != `` { // 如果存在不可信设备,那么将其下架
+		global.Redis.Del(context.TODO(), utils.SYSTEM_LOGIN_TOKEN+noTrustLoginToken)
+	}
+
+	// 如果当前是不可信设备,那么将其加入到不可信名单
+	if !isRemember {
+		noTrustLoginKey := fmt.Sprint(utils.SYSTEM_LOGIN_TOKEN_NO_TRUST, adminInfo.AdminId)
+		global.Redis.Set(context.TODO(), noTrustLoginKey, token, 120*time.Minute)
+	}
+
+	//更新用户的最新登录时间
+	adminInfo.LastLoginTime = time.Now()
+	err = adminInfo.Update([]string{"last_login_time"})
+	if err != nil {
+		errMsg = "更新登录信息失败"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	ret.AdminName = adminInfo.AdminName
+	ret.RealName = adminInfo.RealName
+	ret.AdminId = adminInfo.AdminId
+	ret.Token = token
+	if password == utils.DefaultPwd {
+		ret.ChangePwd = true
+	}
+	return
+}

+ 282 - 0
services/system/sys_dept.go

@@ -0,0 +1,282 @@
+package system
+
+import (
+	"errors"
+	"hongze/hrms_api/models/base"
+	"hongze/hrms_api/models/system"
+	"hongze/hrms_api/utils"
+)
+
+func DeptTreeList(page base.IPage, req *system.DeptListReq) (count int64, list []*system.DeptListItemResp, err error, errMsg string) {
+	dept := new(system.SysDept)
+
+	var condition string
+	var pars []interface{}
+
+	//根据名称找到所有最顶层的节点
+	var finalRootIds []int64
+	if req.DeptName != "" {
+		var rootIds []int64
+		deptName := "%" + req.DeptName + "%"
+		relationList, tErr := dept.GetDeptByName(deptName)
+		if tErr != nil {
+			errMsg = "查询部门出错"
+			err = errors.New(errMsg + "Err:" + tErr.Error())
+			return
+		}
+		if len(relationList) == 0 { //查不到相关记录
+			return
+		}
+		for _, v := range relationList {
+			if v.RootId == 0 {
+				rootIds = append(rootIds, v.DeptId)
+			} else {
+				rootIds = append(rootIds, v.RootId)
+			}
+		}
+		//分页处理 查询最顶层结点,得到最终的顶层结点
+		condition = "parent_id=0 and dept_id in ?"
+		pars = append(pars, rootIds)
+	} else {
+		condition = "parent_id=0"
+	}
+	count, rootList, err := dept.SelectPage(page, condition, pars)
+	if err != nil {
+		errMsg = "查询部门列表出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	if len(rootList) == 0 { //查不到相关记录
+		return
+	}
+	for _, v := range rootList {
+		finalRootIds = append(finalRootIds, v.DeptId)
+	}
+	//查询所有该顶层节点下的子节点
+	fCondition := "root_id in ? or dept_id in ?"
+	fPars := make([]interface{}, 0)
+	fPars = append(fPars, finalRootIds, finalRootIds)
+	orderStr := "sort asc, dept_id desc"
+	childList, err := dept.GetDeptListByCondition(fCondition, fPars, orderStr)
+	if err != nil {
+		errMsg = "查询部门列表出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+
+	list = make([]*system.DeptListItemResp, 0)
+	for _, v := range childList {
+		tmp := &system.DeptListItemResp{
+			DeptId:     v.DeptId,
+			ParentId:   v.ParentId,
+			DeptName:   v.DeptName,
+			Sort:       v.Sort,
+			CreateTime: utils.TimeTransferString(utils.FormatDateTime, v.CreateTime),
+			ModifyTime: utils.TimeTransferString(utils.FormatDateTime, v.ModifyTime),
+			Children:   nil,
+		}
+		list = append(list, tmp)
+	}
+	//组装返回值
+	list = getDeptTreeRecursive(list, 0)
+	return
+}
+
+func getDeptTreeRecursive(list []*system.DeptListItemResp, parentId int64) []*system.DeptListItemResp {
+	res := make([]*system.DeptListItemResp, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			v.Children = getDeptTreeRecursive(list, v.DeptId)
+			res = append(res, v)
+		}
+	}
+	return res
+}
+
+// DeleteDept 删除部门,连同删除子部门
+func DeleteDept(req *system.DelSysDeptReq) (err error, errMsg string) {
+	dept := new(system.SysDept)
+	//找出当前层级,如果当前是最顶层,则批量删除和顶层相关的所有部门
+	deptInfo, err := dept.GetDeptByDeptId(req.DeptId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			errMsg = "部门不存在"
+			err = errors.New(errMsg + "Err:" + err.Error())
+			return
+		}
+		errMsg = "查询部门出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	admin := new(system.SysAdmin)
+	var admins []*system.SysAdmin
+	var depts []*system.SysDept
+	var deptIdMap map[int64][]int64
+	if deptInfo.ParentId > 0 {
+		//如果不是最顶层,则构建树形图,得到需要删除的dept_id
+		//查询所有该顶层节点下的子节点
+		deptIdMap, err, errMsg = GetChildDeptIds(deptInfo.RootId)
+		if err != nil {
+			return err, errMsg
+		}
+		cond := "dept_id in ?"
+		pars := make([]interface{}, 0)
+		pars = append(pars, deptIdMap[deptInfo.DeptId])
+		//验证是否存在绑定的员工账号
+		admins, err = admin.GetAdminListByCondition(cond, pars)
+		if err != nil {
+			errMsg = "查询部门下的用户出错"
+			err = errors.New(errMsg + "Err:" + err.Error())
+			return
+		}
+		if len(admins) > 0 {
+			errMsg = "该部门绑定了员工,不可删除"
+			err = errors.New(errMsg)
+			return
+		}
+		err = dept.DeleteByCondition(cond, pars)
+	} else {
+		//如果是最顶层,则删除所有相关的dept_id
+		cond := "dept_id = ? or root_id = ?"
+		pars := make([]interface{}, 0)
+		pars = append(pars, deptInfo.DeptId, deptInfo.DeptId)
+		depts, err = dept.GetDeptListByCondition(cond, pars, "")
+		if err != nil {
+			errMsg = "查询部门信息出错"
+			err = errors.New(errMsg + "Err:" + err.Error())
+			return
+		}
+		var deptIds []int64
+		for _, v := range depts {
+			deptIds = append(deptIds, v.DeptId)
+		}
+		//验证是否存在绑定的员工账号
+		aCond := "dept_id in ?"
+		aPars := make([]interface{}, 0)
+		aPars = append(aPars, deptIds)
+		admins, err = admin.GetAdminListByCondition(aCond, aPars)
+		if err != nil {
+			errMsg = "查询部门下的员工出错"
+			err = errors.New(errMsg + "Err:" + err.Error())
+			return
+		}
+		if len(admins) > 0 {
+			errMsg = "该部门绑定了员工,不可删除"
+			err = errors.New(errMsg)
+			return
+		}
+		err = dept.DeleteByCondition(cond, pars)
+	}
+	return
+}
+
+func GetChildDeptIds(rootId int64) (deptIdMap map[int64][]int64, err error, errMsg string) {
+	dept := new(system.SysDept)
+	fCondition := ""
+	fPars := make([]interface{}, 0)
+	if rootId > 0 {
+		fCondition = "root_id = ? or dept_id = ?"
+		fPars = append(fPars, rootId, rootId)
+	}
+
+	orderStr := "level desc"
+	childList, tErr := dept.GetDeptListByCondition(fCondition, fPars, orderStr)
+	if tErr != nil {
+		errMsg = "查询部门列表出错"
+		err = errors.New(errMsg + "Err:" + tErr.Error())
+		return
+	}
+
+	deptIdMap = make(map[int64][]int64)
+	for _, v := range childList {
+		deptIdMap[v.DeptId] = append(deptIdMap[v.DeptId], v.DeptId)
+		if v.ParentId > 0 {
+			deptIdMap[v.ParentId] = append(deptIdMap[v.ParentId], deptIdMap[v.DeptId]...)
+		}
+	}
+	return
+}
+
+func GetChildDeptNames() (deptNameMap map[int64]string, err error, errMsg string) {
+	dept := new(system.SysDept)
+	fCondition := ""
+	fPars := make([]interface{}, 0)
+
+	orderStr := "level asc"
+	childList, tErr := dept.GetDeptListByCondition(fCondition, fPars, orderStr)
+	if tErr != nil {
+		errMsg = "查询部门列表出错"
+		err = errors.New(errMsg + "Err:" + tErr.Error())
+		return
+	}
+
+	deptNameMap = make(map[int64]string)
+	for _, v := range childList {
+		if v.ParentId > 0 {
+			deptNameMap[v.DeptId] = deptNameMap[v.ParentId] + "/" + v.DeptName
+		} else {
+			deptNameMap[v.DeptId] = v.DeptName
+		}
+	}
+	return
+}
+
+func DeptAdminTreeList() (list []*system.DeptAdminListItem, err error, errMsg string) {
+	dept := new(system.SysDept)
+	var condition string
+	var pars []interface{}
+	childList, err := dept.GetDeptListByCondition(condition, pars, "")
+	if err != nil {
+		errMsg = "查询部门列表出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	if len(childList) == 0 { //查不到相关记录
+		return
+	}
+	// 查询管理员信息
+	admin := new(system.SysAdmin)
+	var list1 []*system.DeptAdminListItem
+	adminList, err := admin.GetAdminListByCondition(condition, pars)
+	adminMap := make(map[int64][]*system.DeptAdminListItem)
+	for _, v := range adminList {
+		tmp1 := &system.DeptAdminListItem{
+			AdminId:  v.AdminId,
+			Name:     v.RealName,
+			DeptId:   int64(v.AdminId) + 1000,
+			ParentId: v.DeptId,
+			Sort:     0,
+			Children: nil,
+		}
+		list1 = append(list1, tmp1)
+	}
+	list = make([]*system.DeptAdminListItem, 0)
+	for _, v := range childList {
+		tmp := &system.DeptAdminListItem{
+			DeptId:   v.DeptId,
+			Name:     v.DeptName,
+			ParentId: v.ParentId,
+			Sort:     v.Sort,
+			Children: nil,
+		}
+		if c, ok := adminMap[v.DeptId]; ok {
+			tmp.Children = c
+		}
+		list = append(list, tmp)
+	}
+	list = append(list, list1...)
+	//组装返回值
+	list = getDeptAdminTreeRecursive(list, 0)
+	return
+}
+
+func getDeptAdminTreeRecursive(list []*system.DeptAdminListItem, parentId int64) []*system.DeptAdminListItem {
+	res := make([]*system.DeptAdminListItem, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			v.Children = getDeptAdminTreeRecursive(list, v.DeptId)
+			res = append(res, v)
+		}
+	}
+	return res
+}

+ 254 - 0
services/system/sys_menu.go

@@ -0,0 +1,254 @@
+package system
+
+import (
+	"errors"
+	"hongze/hrms_api/models/base"
+	"hongze/hrms_api/models/system"
+	"hongze/hrms_api/utils"
+)
+
+//MenuList 只返回当前登录管理员有权限的菜单列表,不返回按钮
+func MenuList(adminInfo *system.SysAdmin) (list []*system.MenuListItemResp, err error, errMsg string) {
+	//查询有权限的菜单
+	roleMenu := new(system.SysRoleMenu)
+	cond := " rm.role_id = ?"
+	pars := make([]interface{}, 0)
+	pars = append(pars, adminInfo.RoleId)
+	roleHasMenuList, err := roleMenu.GetMenuListByCondition(cond, pars)
+	if err != nil {
+		errMsg = "查询角色绑定的菜单出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	var menuIds []int64
+	for _, v := range roleHasMenuList {
+		menuIds = append(menuIds, v.MenuId)
+	}
+	//查询其他级别的菜单列表
+	menu := new(system.SysMenu)
+	var condition string
+	par := make([]interface{}, 0)
+	condition = " menu_type = 0 and menu_id in ?"
+	par = append(par, menuIds)
+	orderStr := "sort asc, create_time, menu_id desc"
+	//遍历排序好的菜单列表,,返回每个菜单结点,不包含按钮类型
+	childList, err := menu.GetMenuListByCondition(condition, par, orderStr)
+	if err != nil {
+		return
+	}
+	list = make([]*system.MenuListItemResp, 0)
+	for _, v := range childList {
+		tmp := &system.MenuListItemResp{
+			MenuId:       v.MenuId,
+			ParentId:     v.ParentId,
+			Name:         v.Name,
+			Sort:         v.Sort,
+			Path:         v.Path,
+			PathName:     v.PathName,
+			IconPath:     v.IconPath,
+			Component:    v.Component,
+			Hidden:       v.Hidden,
+			HiddenLayout: v.HiddenLayout,
+			Level:        v.Level,
+			MenuType:     v.MenuType,
+			ButtonCode:   v.ButtonCode,
+			CreateTime:   utils.TimeTransferString(utils.FormatDateTime, v.CreateTime),
+			ModifyTime:   utils.TimeTransferString(utils.FormatDateTime, v.ModifyTime),
+			HasBind:      true,
+			Children:     nil,
+		}
+		list = append(list, tmp)
+	}
+	//组装返回值
+	list = getMenuTreeRecursive(list, 0)
+	return
+}
+
+func AllMenuList(page base.IPage, req *system.MenuListReq) (count int64, list []*system.MenuListItemResp, err error, errMsg string) {
+	//查询一级菜单的数量和列表
+	//查询其他级别的菜单列表
+	menu := new(system.SysMenu)
+	var condition string
+	pars := make([]interface{}, 0)
+	var finalRootIds []int64
+	if req.Name != "" {
+		var rootIds []int64
+		name := "%" + req.Name + "%"
+		relationList, tErr := menu.GetMenuByName(name)
+		if tErr != nil {
+			errMsg = "查询菜单出错"
+			err = errors.New(errMsg + "Err:" + tErr.Error())
+			return
+		}
+		if len(relationList) == 0 { //查不到相关记录
+			return
+		}
+		for _, v := range relationList {
+			if v.RootId == 0 {
+				rootIds = append(rootIds, v.MenuId)
+			} else {
+				rootIds = append(rootIds, v.RootId)
+			}
+		}
+		//分页处理 查询最顶层结点,得到最终的顶层结点
+		condition = "parent_id=0 and menu_id in ?"
+		pars = append(pars, rootIds)
+	} else {
+		condition = " parent_id = 0"
+	}
+
+	count, rootList, err := menu.SelectPage(page, condition, pars)
+	if err != nil {
+		errMsg = "查询菜单列表出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	if len(rootList) == 0 { //查不到相关记录
+		return
+	}
+	for _, v := range rootList {
+		finalRootIds = append(finalRootIds, v.MenuId)
+	}
+	fCondition := " 1=1"
+	fPars := make([]interface{}, 0)
+	if req.HideLevel3 == 1 {
+		fCondition += " and level <=1 "
+	}
+	if req.HideButton == 1 {
+		fCondition += " and menu_type = 0"
+	}
+	//查询所有该顶层节点下的子节点
+	fCondition += " and (root_id in ? or menu_id in ?)"
+	fPars = append(fPars, finalRootIds, finalRootIds)
+	orderStr := "sort asc, create_time, menu_id desc"
+	childList, err := menu.GetMenuListByCondition(fCondition, fPars, orderStr)
+	if err != nil {
+		errMsg = "查询菜单列表出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	list = make([]*system.MenuListItemResp, 0)
+	for _, v := range childList {
+		tmp := &system.MenuListItemResp{
+			MenuId:       v.MenuId,
+			ParentId:     v.ParentId,
+			Name:         v.Name,
+			Sort:         v.Sort,
+			Path:         v.Path,
+			PathName:     v.PathName,
+			IconPath:     v.IconPath,
+			Component:    v.Component,
+			Hidden:       v.Hidden,
+			HiddenLayout: v.HiddenLayout,
+			Level:        v.Level,
+			MenuType:     v.MenuType,
+			ButtonCode:   v.ButtonCode,
+			CreateTime:   utils.TimeTransferString(utils.FormatDateTime, v.CreateTime),
+			ModifyTime:   utils.TimeTransferString(utils.FormatDateTime, v.ModifyTime),
+			Children:     nil,
+		}
+		list = append(list, tmp)
+	}
+
+	//组装返回值
+	list = getMenuTreeRecursive(list, 0)
+	return
+}
+
+func getMenuTreeRecursive(list []*system.MenuListItemResp, parentId int64) []*system.MenuListItemResp {
+	res := make([]*system.MenuListItemResp, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			v.Children = getMenuTreeRecursive(list, v.MenuId)
+			res = append(res, v)
+		}
+	}
+	return res
+}
+
+//ButtonList 只返回当前登录管理员有权限的按钮列表,不返回按钮
+func ButtonList(adminInfo *system.SysAdmin, req *system.MenuShortListItem) (list []*system.SysMenu, err error, errMsg string) {
+	//查询有权限的菜单
+	roleMenu := new(system.SysRoleMenu)
+	cond := " rm.role_id = ?"
+	pars := make([]interface{}, 0)
+	pars = append(pars, adminInfo.RoleId)
+	roleHasMenuList, err := roleMenu.GetMenuListByCondition(cond, pars)
+	if err != nil {
+		errMsg = "查询角色绑定的菜单出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	var menuIds []int64
+	for _, v := range roleHasMenuList {
+		menuIds = append(menuIds, v.MenuId)
+	}
+	//查询其他级别的菜单列表
+	menu := new(system.SysMenu)
+	var condition string
+	par := make([]interface{}, 0)
+	condition = " menu_type = 1 and menu_id in ?"
+	par = append(par, menuIds)
+	if req.MenuId > 0 {
+		condition += " and parent_id = ?"
+		par = append(par, req.MenuId)
+	}
+	//遍历排序好的菜单列表,,返回每个菜单结点,不包含按钮类型
+	list, err = menu.GetMenuListByCondition(condition, par, "")
+	if err != nil {
+		errMsg = "查询菜单按钮列表出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	return
+}
+
+// DeleteMenu 删除菜单,连同删除子菜单
+func DeleteMenu(req *system.MenuShortListItem) (err error, errMsg string) {
+	menu := new(system.SysMenu)
+	//找出当前层级,如果当前是最顶层,则批量删除和顶层相关的所有部门
+	menuInfo, err := menu.GetMenuByMenuId(req.MenuId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			errMsg = "菜单不存在或者已删除"
+			err = errors.New(errMsg + "Err:" + err.Error())
+			return
+		}
+		errMsg = "查询菜单出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	if menuInfo.ParentId > 0 {
+		//如果不是最顶层,则构建树形图,得到需要删除的menu_id
+		//查询所有该顶层节点下的子节点
+		fCondition := "root_id = ? or menu_id = ?"
+		fPars := make([]interface{}, 0)
+		fPars = append(fPars, menuInfo.RootId, menuInfo.RootId)
+		orderStr := "level desc"
+		childList, tErr := menu.GetMenuListByCondition(fCondition, fPars, orderStr)
+		if tErr != nil {
+			errMsg = "查询菜单列表出错"
+			err = errors.New(errMsg + "Err:" + tErr.Error())
+			return
+		}
+
+		menuIdMap := make(map[int64][]int64)
+		for _, v := range childList {
+			menuIdMap[v.MenuId] = append(menuIdMap[v.MenuId], v.MenuId)
+			if v.ParentId > 0 {
+				menuIdMap[v.ParentId] = append(menuIdMap[v.ParentId], menuIdMap[v.MenuId]...)
+			}
+		}
+		cond := "menu_id in ?"
+		pars := make([]interface{}, 0)
+		pars = append(pars, menuIdMap[menuInfo.MenuId])
+		err = menu.DeleteByCondition(cond, pars)
+	} else {
+		//如果是最顶层,则删除所有相关的menu_id
+		cond := "menu_id = ? or root_id = ?"
+		pars := make([]interface{}, 0)
+		pars = append(pars, menuInfo.MenuId, menuInfo.MenuId)
+		err = menu.DeleteByCondition(cond, pars)
+	}
+	return
+}

+ 190 - 0
services/system/sys_role.go

@@ -0,0 +1,190 @@
+package system
+
+import (
+	"errors"
+	"hongze/hrms_api/models/system"
+	"hongze/hrms_api/utils"
+	"time"
+)
+
+func AddRole(req *system.SysRoleAddReq) (err error, errMsg string) {
+	role := new(system.SysRole)
+	// 判断角色名称是否已存在
+	_, err = role.GetRoleByRoleName(req.RoleName)
+	if err == nil {
+		errMsg = "角色名称已存在, 请重新输入"
+		err = errors.New(errMsg)
+		return
+	} else {
+		if err != utils.ErrNoRow {
+			errMsg = "查询角色名称出错"
+			err = errors.New(errMsg + "Err:" + err.Error())
+			return
+		}
+		err = nil
+	}
+	// 添加角色
+	role.RoleName = req.RoleName
+	err = role.Add()
+	if err != nil {
+		errMsg = "添加角色出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	return
+}
+
+func EditRoleMenu(req *system.SysRoleMenuEditReq) (err error, errMsg string) {
+	role := new(system.SysRole)
+	// 判断角色名称是否已存在
+	_, err = role.GetRoleByRoleId(req.RoleId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			errMsg = "角色不存在或者已经被删除"
+			err = errors.New(errMsg + "Err:" + err.Error())
+			return
+		}
+		errMsg = "查询角色出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	//删除绑定的所有菜单
+	roleMenu := new(system.SysRoleMenu)
+	cond := "role_id = ?"
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.RoleId)
+	err = roleMenu.DeleteByCondition(cond, pars)
+	if err != nil {
+		errMsg = "删除角色原先绑定的菜单出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	//给角色添加菜单权限
+	list := make([]*system.SysRoleMenu, 0)
+	if len(req.MenuIdList) > 0 {
+		for _, v := range req.MenuIdList {
+			item := new(system.SysRoleMenu)
+			item.RoleId = req.RoleId
+			item.MenuId = v
+			item.CreateTime = time.Now()
+			item.ModifyTime = time.Now()
+			list = append(list, item)
+		}
+		if err = roleMenu.AddMulti(list); err != nil {
+			errMsg = "给角色添加菜单权限出错"
+			err = errors.New(errMsg + "Err:" + err.Error())
+			return
+		}
+	}
+	return
+}
+
+func RoleMenuList(req *system.SysRoleMenuListReq) (ret system.SysRoleMenuResp, err error, errMsg string) {
+	//查询角色绑定的菜单
+	roleMenu := new(system.SysRoleMenu)
+	cond := " rm.role_id = ?"
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.RoleId)
+	roleHasMenuList, err := roleMenu.GetMenuListByCondition(cond, pars)
+	if err != nil {
+		errMsg = "查询角色绑定的菜单出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	RoleMenuMap := make(map[int64]struct{}, 0)
+	HasMenuIds := make([]int64, 0)
+	for _, v := range roleHasMenuList {
+		RoleMenuMap[v.MenuId] = struct{}{}
+		HasMenuIds = append(HasMenuIds, v.MenuId)
+	}
+	//查询所有菜单列表
+	menu := new(system.SysMenu)
+	orderStr := "sort asc, create_time, menu_id desc"
+	childList, err := menu.GetMenuListByCondition("", []interface{}{}, orderStr)
+	if err != nil {
+		return
+	}
+	list := make([]*system.MenuListItemResp, 0)
+	for _, v := range childList {
+		tmp := &system.MenuListItemResp{
+			MenuId:       v.MenuId,
+			ParentId:     v.ParentId,
+			Name:         v.Name,
+			Sort:         v.Sort,
+			Path:         v.Path,
+			PathName:     v.PathName,
+			IconPath:     v.IconPath,
+			Component:    v.Component,
+			Hidden:       v.Hidden,
+			HiddenLayout: v.HiddenLayout,
+			Level:        v.Level,
+			MenuType:     v.MenuType,
+			ButtonCode:   v.ButtonCode,
+			CreateTime:   utils.TimeTransferString(utils.FormatDateTime, v.CreateTime),
+			ModifyTime:   utils.TimeTransferString(utils.FormatDateTime, v.ModifyTime),
+			Children:     nil,
+		}
+		if _, ok := RoleMenuMap[tmp.MenuId]; ok {
+			tmp.HasBind = true
+		}
+		list = append(list, tmp)
+	}
+	//组装返回值
+	list = getMenuTreeRecursive(list, 0)
+	ret.List = list
+	ret.ChoiceList = HasMenuIds
+	return
+}
+
+func DeleteRole(req *system.SysRoleDelReq) (err error, errMsg string) {
+	role := new(system.SysRole)
+	// 判断角色名称是否已存在
+	_, err = role.GetRoleByRoleId(req.RoleId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			errMsg = "角色不存在或者已经被删除"
+			err = errors.New(errMsg + "Err:" + err.Error())
+			return
+		}
+		errMsg = "查询角色出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	//删除绑定的所有菜单
+	roleMenu := new(system.SysRoleMenu)
+	cond := "role_id = ?"
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.RoleId)
+	err = roleMenu.DeleteByCondition(cond, pars)
+	if err != nil {
+		errMsg = "删除角色原先绑定的菜单出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	//查询与角色绑定的所有账号
+	admin := new(system.SysAdmin)
+	aCond := "role_id = ?"
+	aPars := make([]interface{}, 0)
+	aPars = append(pars, req.RoleId)
+
+	admins, err := admin.GetAdminListByCondition(aCond, aPars)
+	if err != nil {
+		errMsg = "查询角色对应的用户出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	if len(admins) > 0 {
+		errMsg = "有账号绑定该角色,不可删除"
+		err = errors.New(errMsg)
+		return
+	}
+	// 删除角色
+	role.RoleId = req.RoleId
+	err = role.Delete()
+	if err != nil {
+		errMsg = "删除角色出错"
+		err = errors.New(errMsg + "Err:" + err.Error())
+		return
+	}
+	return
+}

+ 0 - 0
static/static.txt


+ 72 - 0
task/task.go

@@ -0,0 +1,72 @@
+package task
+
+import (
+	"fmt"
+	"hongze/hrms_api/global"
+	"hongze/hrms_api/services/alarm_msg"
+	"hongze/hrms_api/utils"
+	"os"
+	"runtime"
+	"sync"
+	"time"
+)
+
+type TaskFunc func(params ...interface{})
+
+var taskList chan *Executor //任务列表
+
+var once sync.Once
+
+func GetTaskList() chan *Executor {
+	once.Do(func() {
+		taskList = make(chan *Executor, 1000)
+	})
+	return taskList
+}
+
+type Executor struct {
+	f      TaskFunc
+	params []interface{}
+}
+
+func (e *Executor) Exec() { //执行任务
+	go func() {
+		defer func() {
+			if err := recover(); err != nil {
+				stack := ""
+				msg := fmt.Sprintf("当前进程pid:%d; 父进程ppid:%d", os.Getpid(), os.Getppid())
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				msg = fmt.Sprintf("The params data is %v", e.params)
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				msg = fmt.Sprintf("Handler crashed with error %v", err)
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				for i := 1; ; i++ {
+					_, file, line, ok := runtime.Caller(i)
+					if !ok {
+						break
+					}
+					global.LOG.Critical(fmt.Sprintf("%s:%d", file, line))
+					stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d</br>", file, line))
+				}
+				fmt.Println("stack:", stack)
+				//go services.SendEmail(utils.APPNAME+"崩了"+time.Now().Format("2006-01-02 15:04:05"), stack, utils.EmailSendToUsers)
+				go alarm_msg.SendAlarmMsg(utils.APPNAME+"崩了"+time.Now().Format(utils.FormatDateTime)+";Msg:"+stack, 2)
+			}
+		}()
+		//time.Sleep(60*time.Second)
+		//fmt.Println("i am here new")
+		e.f(e.params...)
+	}()
+
+}
+
+func NewExecutor(f TaskFunc, params []interface{}) *Executor {
+	return &Executor{f: f, params: params}
+}
+
+func Task(f TaskFunc, params ...interface{}) {
+	taskList <- NewExecutor(f, params)
+}

+ 1029 - 0
utils/common.go

@@ -0,0 +1,1029 @@
+package utils
+
+import (
+	"bufio"
+	"crypto/md5"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"gorm.io/gorm"
+	"image"
+	"image/png"
+	"io"
+	"math"
+	"math/rand"
+	"net"
+	"net/http"
+	"os"
+	"os/exec"
+	"path"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var ErrNoRow = gorm.ErrRecordNotFound
+
+/*var (
+	KEY = []byte("OsL5s35Xv6")
+)*/
+
+// 发放token
+func GenToken(account string) (accessToken string, err error) {
+	/*token := jwt.New(jwt.SigningMethodHS256)
+	token.Claims = &jwt.StandardClaims{
+		NotBefore: int64(time.Now().Unix()),
+		ExpiresAt: int64(time.Now().Unix() + 90*24*60*60),
+		Issuer:    "hrms_api",
+		Subject:   account,
+	}
+	accessToken, err = token.SignedString(KEY)*/
+	timeUnix := time.Now().Unix()
+	timeUnixStr := strconv.FormatInt(timeUnix, 10)
+	accessToken = MD5(account) + MD5(timeUnixStr)
+	return
+}
+
+//随机数种子
+var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
+
+// GetRandString 获取随机字符串
+func GetRandString(size int) string {
+	allLetterDigit := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "!", "@", "#", "$", "%", "^", "&", "*"}
+	randomSb := ""
+	digitSize := len(allLetterDigit)
+	for i := 0; i < size; i++ {
+		randomSb += allLetterDigit[rnd.Intn(digitSize)]
+	}
+	return randomSb
+}
+
+// GetRandStringNoSpecialChar
+func GetRandStringNoSpecialChar(size int) string {
+	allLetterDigit := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
+	randomSb := ""
+	digitSize := len(allLetterDigit)
+	for i := 0; i < size; i++ {
+		randomSb += allLetterDigit[rnd.Intn(digitSize)]
+	}
+	return randomSb
+}
+
+// StringsToJSON
+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
+}
+
+// ToString 序列化
+func ToString(v interface{}) string {
+	data, _ := json.Marshal(v)
+	return string(data)
+}
+
+// MD5 md5加密
+func MD5(data string) string {
+	m := md5.Sum([]byte(data))
+	return hex.EncodeToString(m[:])
+}
+
+// GetRandDigit 获取数字随机字符
+func GetRandDigit(n int) string {
+	return fmt.Sprintf("%0"+strconv.Itoa(n)+"d", rnd.Intn(int(math.Pow10(n))))
+}
+
+// GetRandNumber 获取随机数
+func GetRandNumber(n int) int {
+	return rnd.Intn(n)
+}
+
+// GetRandInt
+func GetRandInt(min, max int) int {
+	if min >= max || min == 0 || max == 0 {
+		return max
+	}
+	return rand.Intn(max-min) + min
+}
+
+// GetToday 获取今天的随机字符
+func GetToday(format string) string {
+	today := time.Now().Format(format)
+	return today
+}
+
+// GetTodayLastSecond 获取今天剩余秒数
+func GetTodayLastSecond() time.Duration {
+	today := GetToday(FormatDate) + " 23:59:59"
+	end, _ := time.ParseInLocation(FormatDateTime, today, time.Local)
+	return time.Duration(end.Unix()-time.Now().Local().Unix()) * time.Second
+}
+
+// GetBrithDate 处理出生日期函数
+func GetBrithDate(idcard string) string {
+	l := len(idcard)
+	var s string
+	if l == 15 {
+		s = "19" + idcard[6:8] + "-" + idcard[8:10] + "-" + idcard[10:12]
+		return s
+	}
+	if l == 18 {
+		s = idcard[6:10] + "-" + idcard[10:12] + "-" + idcard[12:14]
+		return s
+	}
+	return GetToday(FormatDate)
+}
+
+// WhichSexByIdcard 处理性别
+func WhichSexByIdcard(idcard string) string {
+	var sexs = [2]string{"女", "男"}
+	length := len(idcard)
+	if length == 18 {
+		sex, _ := strconv.Atoi(string(idcard[16]))
+		return sexs[sex%2]
+	} else if length == 15 {
+		sex, _ := strconv.Atoi(string(idcard[14]))
+		return sexs[sex%2]
+	}
+	return "男"
+}
+
+// SubFloatToString 截取小数点后几位
+func SubFloatToString(f float64, m int) string {
+	n := strconv.FormatFloat(f, 'f', -1, 64)
+	if n == "" {
+		return ""
+	}
+	if m >= len(n) {
+		return n
+	}
+	newn := strings.Split(n, ".")
+	if m == 0 {
+		return newn[0]
+	}
+	if len(newn) < 2 || m >= len(newn[1]) {
+		return n
+	}
+	return newn[0] + "." + newn[1][:m]
+}
+
+// SubFloatToFloat 截取小数点后几位
+func SubFloatToFloat(f float64, m int) float64 {
+	newn := SubFloatToString(f, m)
+	newf, _ := strconv.ParseFloat(newn, 64)
+	return newf
+}
+
+// SubFloatToFloatStr 截取小数点后几位
+func SubFloatToFloatStr(f float64, m int) string {
+	newn := SubFloatToString(f, m)
+	return newn
+}
+
+// GetYearDiffer 获取相差时间-年
+func GetYearDiffer(start_time, end_time string) int {
+	t1, _ := time.ParseInLocation("2006-01-02", start_time, time.Local)
+	t2, _ := time.ParseInLocation("2006-01-02", end_time, time.Local)
+	age := t2.Year() - t1.Year()
+	if t2.Month() < t1.Month() || (t2.Month() == t1.Month() && t2.Day() < t1.Day()) {
+		age--
+	}
+	return age
+}
+
+// GetSecondDifferByTime 获取相差时间-秒
+func GetSecondDifferByTime(start_time, end_time time.Time) int64 {
+	diff := end_time.Unix() - start_time.Unix()
+	return diff
+}
+
+// FixFloat
+func FixFloat(f float64, m int) float64 {
+	newn := SubFloatToString(f+0.00000001, m)
+	newf, _ := strconv.ParseFloat(newn, 64)
+	return newf
+}
+
+// StrListToString 将字符串数组转化为逗号分割的字符串形式  ["str1","str2","str3"] >>> "str1,str2,str3"
+func StrListToString(strList []string) (str string) {
+	if len(strList) > 0 {
+		for k, v := range strList {
+			if k == 0 {
+				str = v
+			} else {
+				str = str + "," + v
+			}
+		}
+		return
+	}
+	return ""
+}
+
+// ValidateEmailFormatat 校验邮箱格式
+func ValidateEmailFormatat(email string) bool {
+	reg := regexp.MustCompile(RegularEmail)
+	return reg.MatchString(email)
+}
+
+// ValidateMobileFormatat 验证是否是手机号
+func ValidateMobileFormatat(mobileNum string) bool {
+	reg := regexp.MustCompile(RegularMobile)
+	return reg.MatchString(mobileNum)
+}
+
+// FileIsExist 判断文件是否存在
+func FileIsExist(filePath string) bool {
+	_, err := os.Stat(filePath)
+	return err == nil || os.IsExist(err)
+}
+
+// GetImgExt 获取图片扩展名
+func GetImgExt(file string) (ext string, err error) {
+	var headerByte []byte
+	headerByte = make([]byte, 8)
+	fd, err := os.Open(file)
+	if err != nil {
+		return "", err
+	}
+	defer fd.Close()
+	_, err = fd.Read(headerByte)
+	if err != nil {
+		return "", err
+	}
+	xStr := fmt.Sprintf("%x", headerByte)
+	switch {
+	case xStr == "89504e470d0a1a0a":
+		ext = ".png"
+	case xStr == "0000010001002020":
+		ext = ".ico"
+	case xStr == "0000020001002020":
+		ext = ".cur"
+	case xStr[:12] == "474946383961" || xStr[:12] == "474946383761":
+		ext = ".gif"
+	case xStr[:10] == "0000020000" || xStr[:10] == "0000100000":
+		ext = ".tga"
+	case xStr[:8] == "464f524d":
+		ext = ".iff"
+	case xStr[:8] == "52494646":
+		ext = ".ani"
+	case xStr[:4] == "4d4d" || xStr[:4] == "4949":
+		ext = ".tiff"
+	case xStr[:4] == "424d":
+		ext = ".bmp"
+	case xStr[:4] == "ffd8":
+		ext = ".jpg"
+	case xStr[:2] == "0a":
+		ext = ".pcx"
+	default:
+		ext = ""
+	}
+	return ext, nil
+}
+
+// SaveImage 保存图片
+func SaveImage(path string, img image.Image) (err error) {
+	//需要保持的文件
+	imgfile, err := os.Create(path)
+	defer imgfile.Close()
+	// 以PNG格式保存文件
+	err = png.Encode(imgfile, img)
+	return err
+}
+
+// DownloadImage 下载图片
+func DownloadImage(imgUrl string) (filePath string, err error) {
+	imgPath := "./static/imgs/"
+	fileName := path.Base(imgUrl)
+	res, err := http.Get(imgUrl)
+	if err != nil {
+		fmt.Println("A error occurred!")
+		return
+	}
+	defer res.Body.Close()
+	// 获得get请求响应的reader对象
+	reader := bufio.NewReaderSize(res.Body, 32*1024)
+
+	filePath = imgPath + fileName
+	file, err := os.Create(filePath)
+	if err != nil {
+		return
+	}
+	// 获得文件的writer对象
+	writer := bufio.NewWriter(file)
+
+	written, _ := io.Copy(writer, reader)
+	fmt.Printf("Total length: %d \n", written)
+	return
+}
+
+// SaveBase64ToFile 保存base64数据为文件
+func SaveBase64ToFile(content, path string) error {
+	data, err := base64.StdEncoding.DecodeString(content)
+	if err != nil {
+		return err
+	}
+	f, err := os.Create(path)
+	defer f.Close()
+	if err != nil {
+		return err
+	}
+	f.Write(data)
+	return nil
+}
+
+// SaveBase64ToFileBySeek
+func SaveBase64ToFileBySeek(content, path string) (err error) {
+	data, err := base64.StdEncoding.DecodeString(content)
+	exist, err := PathExists(path)
+	if err != nil {
+		return
+	}
+	if !exist {
+		f, err := os.Create(path)
+		if err != nil {
+			return err
+		}
+		n, _ := f.Seek(0, 2)
+		// 从末尾的偏移量开始写入内容
+		_, err = f.WriteAt([]byte(data), n)
+		defer f.Close()
+	} else {
+		f, err := os.OpenFile(path, os.O_WRONLY, 0644)
+		if err != nil {
+			return err
+		}
+		n, _ := f.Seek(0, 2)
+		// 从末尾的偏移量开始写入内容
+		_, err = f.WriteAt([]byte(data), n)
+		defer f.Close()
+	}
+
+	return nil
+}
+
+// StartIndex 开始下标
+func StartIndex(page, pagesize int) int {
+	if page > 1 {
+		return (page - 1) * pagesize
+	}
+	return 0
+}
+
+// PageCount
+func PageCount(count, pagesize int) int {
+	if count%pagesize > 0 {
+		return count/pagesize + 1
+	} else {
+		return count / pagesize
+	}
+}
+
+// TrimHtml
+func TrimHtml(src string) string {
+	//将HTML标签全转换成小写
+	re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
+	src = re.ReplaceAllStringFunc(src, strings.ToLower)
+
+	re, _ = regexp.Compile("\\<img[\\S\\s]+?\\>")
+	src = re.ReplaceAllString(src, "")
+
+	re, _ = regexp.Compile("class[\\S\\s]+?>")
+	src = re.ReplaceAllString(src, "")
+	re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
+	src = re.ReplaceAllString(src, "")
+	return strings.TrimSpace(src)
+}
+
+//1556164246  ->  2019-04-25 03:50:46 +0000
+//timestamp
+// TimeToTimestamp
+func TimeToTimestamp() {
+	fmt.Println(time.Unix(1556164246, 0).Format(FormatDateTime))
+}
+
+func TimeTransferString(format string, t time.Time) string {
+	str := t.Format(format)
+	var emptyT time.Time
+	if str == emptyT.Format(format) {
+		return ""
+	}
+	return str
+}
+
+// ToUnicode
+func ToUnicode(text string) string {
+	textQuoted := strconv.QuoteToASCII(text)
+	textUnquoted := textQuoted[1 : len(textQuoted)-1]
+	return textUnquoted
+}
+
+// VersionToInt
+func VersionToInt(version string) int {
+	version = strings.Replace(version, ".", "", -1)
+	n, _ := strconv.Atoi(version)
+	return n
+}
+
+// IsCheckInList
+func IsCheckInList(list []int, s int) bool {
+	for _, v := range list {
+		if v == s {
+			return true
+		}
+	}
+	return false
+
+}
+
+// round
+func round(num float64) int {
+	return int(num + math.Copysign(0.5, num))
+}
+
+// toFixed
+func toFixed(num float64, precision int) float64 {
+	output := math.Pow(10, float64(precision))
+	return float64(round(num*output)) / output
+}
+
+// GetWilsonScore returns Wilson Score
+func GetWilsonScore(p, n float64) float64 {
+	if p == 0 && n == 0 {
+		return 0
+	}
+
+	return toFixed(((p+1.9208)/(p+n)-1.96*math.Sqrt(p*n/(p+n)+0.9604)/(p+n))/(1+3.8416/(p+n)), 2)
+}
+
+//将中文数字转化成数字,比如 第三百四十五章,返回第345章 不支持一亿及以上
+func ChangeWordsToNum(str string) (numStr string) {
+	words := ([]rune)(str)
+	num := 0
+	n := 0
+	for i := 0; i < len(words); i++ {
+		word := string(words[i : i+1])
+		switch word {
+		case "万":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 10000
+			num = num*10000 + n
+			n = 0
+		case "千":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 1000
+			num += n
+			n = 0
+		case "百":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 100
+			num += n
+			n = 0
+		case "十":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 10
+			num += n
+			n = 0
+		case "一":
+			n += 1
+		case "二":
+			n += 2
+		case "三":
+			n += 3
+		case "四":
+			n += 4
+		case "五":
+			n += 5
+		case "六":
+			n += 6
+		case "七":
+			n += 7
+		case "八":
+			n += 8
+		case "九":
+			n += 9
+		case "零":
+		default:
+			if n > 0 {
+				num += n
+				n = 0
+			}
+			if num == 0 {
+				numStr += word
+			} else {
+				numStr += strconv.Itoa(num) + word
+				num = 0
+			}
+		}
+	}
+	if n > 0 {
+		num += n
+		n = 0
+	}
+	if num != 0 {
+		numStr += strconv.Itoa(num)
+	}
+	return
+}
+
+// Sha1
+func Sha1(data string) string {
+	sha1 := sha1.New()
+	sha1.Write([]byte(data))
+	return hex.EncodeToString(sha1.Sum([]byte("")))
+}
+
+// GetVideoPlaySeconds
+func GetVideoPlaySeconds(videoPath string) (playSeconds float64, err error) {
+	cmd := `ffmpeg -i ` + videoPath + `  2>&1 | grep 'Duration' | cut -d ' ' -f 4 | sed s/,//`
+	out, err := exec.Command("bash", "-c", cmd).Output()
+	if err != nil {
+		return
+	}
+	outTimes := string(out)
+	fmt.Println("outTimes:", outTimes)
+	if outTimes != "" {
+		timeArr := strings.Split(outTimes, ":")
+		h := timeArr[0]
+		m := timeArr[1]
+		s := timeArr[2]
+		hInt, err := strconv.Atoi(h)
+		if err != nil {
+			return playSeconds, err
+		}
+
+		mInt, err := strconv.Atoi(m)
+		if err != nil {
+			return playSeconds, err
+		}
+		s = strings.Trim(s, " ")
+		s = strings.Trim(s, "\n")
+		sInt, err := strconv.ParseFloat(s, 64)
+		if err != nil {
+			return playSeconds, err
+		}
+		playSeconds = float64(hInt)*3600 + float64(mInt)*60 + float64(sInt)
+	}
+	return
+}
+
+// GetMaxTradeCode
+func GetMaxTradeCode(tradeCode string) (maxTradeCode string, err error) {
+	tradeCode = strings.Replace(tradeCode, "W", "", -1)
+	tradeCode = strings.Trim(tradeCode, " ")
+	tradeCodeInt, err := strconv.Atoi(tradeCode)
+	if err != nil {
+		return
+	}
+	tradeCodeInt = tradeCodeInt + 1
+	maxTradeCode = fmt.Sprintf("W%06d", tradeCodeInt)
+	return
+}
+
+// ConvertToFormatDay excel日期字段格式化 yyyy-mm-dd
+func ConvertToFormatDay(excelDaysString string) string {
+	// 2006-01-02 距离 1900-01-01的天数
+	baseDiffDay := 38719 //在网上工具计算的天数需要加2天,什么原因没弄清楚
+	curDiffDay := excelDaysString
+	b, _ := strconv.Atoi(curDiffDay)
+	// 获取excel的日期距离2006-01-02的天数
+	realDiffDay := b - baseDiffDay
+	//fmt.Println("realDiffDay:",realDiffDay)
+	// 距离2006-01-02 秒数
+	realDiffSecond := realDiffDay * 24 * 3600
+	//fmt.Println("realDiffSecond:",realDiffSecond)
+	// 2006-01-02 15:04:05距离1970-01-01 08:00:00的秒数 网上工具可查出
+	baseOriginSecond := 1136185445
+	resultTime := time.Unix(int64(baseOriginSecond+realDiffSecond), 0).Format("2006-01-02")
+	return resultTime
+}
+
+// CheckPwd
+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
+}
+
+// GetMonthStartAndEnd
+func GetMonthStartAndEnd(myYear string, myMonth string) (startDate, endDate string) {
+	// 数字月份必须前置补零
+	if len(myMonth) == 1 {
+		myMonth = "0" + myMonth
+	}
+	yInt, _ := strconv.Atoi(myYear)
+
+	timeLayout := FormatDateTime
+	loc, _ := time.LoadLocation("Local")
+	theTime, _ := time.ParseInLocation(timeLayout, myYear+"-"+myMonth+"-01 00:00:00", loc)
+	newMonth := theTime.Month()
+
+	t1 := time.Date(yInt, newMonth, 1, 0, 0, 0, 0, time.Local).Format("2006-01-02")
+	t2 := time.Date(yInt, newMonth+1, 0, 0, 0, 0, 0, time.Local).Format("2006-01-02")
+	return t1, t2
+}
+
+//TrimStr 移除字符串中的空格
+func TrimStr(str string) (str2 string) {
+	return strings.Replace(str, " ", "", -1)
+}
+
+// StrTimeToTime 字符串转换为time
+func StrTimeToTime(strTime string) time.Time {
+	timeLayout := FormatDateTime         //转化所需模板
+	loc, _ := time.LoadLocation("Local") //重要:获取时区
+	resultTime, _ := time.ParseInLocation(timeLayout, strTime, loc)
+	return resultTime
+}
+
+// StrDateTimeToWeek 字符串类型时间转周几
+func StrDateTimeToWeek(strTime string) string {
+	var WeekDayMap = map[string]string{
+		"Monday":    "周一",
+		"Tuesday":   "周二",
+		"Wednesday": "周三",
+		"Thursday":  "周四",
+		"Friday":    "周五",
+		"Saturday":  "周六",
+		"Sunday":    "周日",
+	}
+	var ctime = StrTimeToTime(strTime).Format("2006-01-02")
+	startday, _ := time.Parse("2006-01-02", ctime)
+	staweek_int := startday.Weekday().String()
+	return WeekDayMap[staweek_int]
+}
+
+// TimeToStrYmd 时间格式转年月日字符串
+func TimeToStrYmd(time2 time.Time) string {
+	var Ymd string
+	year := time2.Year()
+	month := time2.Format("1")
+	day1 := time.Now().Day()
+	Ymd = strconv.Itoa(year) + "年" + month + "月" + strconv.Itoa(day1) + "日"
+	return Ymd
+}
+
+// TimeRemoveHms 时间格式去掉时分秒
+func TimeRemoveHms(strTime string) string {
+	var Ymd string
+	var resultTime = StrTimeToTime(strTime)
+	year := resultTime.Year()
+	month := resultTime.Format("01")
+	day1 := resultTime.Day()
+	Ymd = strconv.Itoa(year) + "." + month + "." + strconv.Itoa(day1)
+	return Ymd
+}
+
+// ArticleLastTime 文章上一次编辑时间
+func ArticleLastTime(strTime string) string {
+	var newTime string
+	stamp, _ := time.ParseInLocation(FormatDateTime, strTime, time.Local)
+	diffTime := time.Now().Unix() - stamp.Unix()
+	if diffTime <= 60 {
+		newTime = "当前"
+	} else if diffTime < 60*60 {
+		newTime = strconv.FormatInt(diffTime/60, 10) + "分钟前"
+	} else if diffTime < 24*60*60 {
+		newTime = strconv.FormatInt(diffTime/(60*60), 10) + "小时前"
+	} else if diffTime < 30*24*60*60 {
+		newTime = strconv.FormatInt(diffTime/(24*60*60), 10) + "天前"
+	} else if diffTime < 12*30*24*60*60 {
+		newTime = strconv.FormatInt(diffTime/(30*24*60*60), 10) + "月前"
+	} else {
+		newTime = "1年前"
+	}
+	return newTime
+}
+
+// ConvertNumToCny 人民币小写转大写
+func ConvertNumToCny(num float64) (str string, err error) {
+	strNum := strconv.FormatFloat(num*100, 'f', 0, 64)
+	sliceUnit := []string{"仟", "佰", "拾", "亿", "仟", "佰", "拾", "万", "仟", "佰", "拾", "元", "角", "分"}
+	// log.Println(sliceUnit[:len(sliceUnit)-2])
+	s := sliceUnit[len(sliceUnit)-len(strNum):]
+	upperDigitUnit := map[string]string{"0": "零", "1": "壹", "2": "贰", "3": "叁", "4": "肆", "5": "伍", "6": "陆", "7": "柒", "8": "捌", "9": "玖"}
+	for k, v := range strNum[:] {
+		str = str + upperDigitUnit[string(v)] + s[k]
+	}
+	reg, err := regexp.Compile(`零角零分$`)
+	str = reg.ReplaceAllString(str, "整")
+
+	reg, err = regexp.Compile(`零角`)
+	str = reg.ReplaceAllString(str, "零")
+
+	reg, err = regexp.Compile(`零分$`)
+	str = reg.ReplaceAllString(str, "整")
+
+	reg, err = regexp.Compile(`零[仟佰拾]`)
+	str = reg.ReplaceAllString(str, "零")
+
+	reg, err = regexp.Compile(`零{2,}`)
+	str = reg.ReplaceAllString(str, "零")
+
+	reg, err = regexp.Compile(`零亿`)
+	str = reg.ReplaceAllString(str, "亿")
+
+	reg, err = regexp.Compile(`零万`)
+	str = reg.ReplaceAllString(str, "万")
+
+	reg, err = regexp.Compile(`零*元`)
+	str = reg.ReplaceAllString(str, "元")
+
+	reg, err = regexp.Compile(`亿零{0, 3}万`)
+	str = reg.ReplaceAllString(str, "^元")
+
+	reg, err = regexp.Compile(`零元`)
+	str = reg.ReplaceAllString(str, "零")
+	return
+}
+
+// GetNowWeekMonday 获取本周周一的时间
+func GetNowWeekMonday() time.Time {
+	offset := int(time.Monday - time.Now().Weekday())
+	if offset == 1 { //正好是周日,但是按照中国人的理解,周日是一周最后一天,而不是一周开始的第一天
+		offset = -6
+	}
+	mondayTime := time.Now().AddDate(0, 0, offset)
+	mondayTime = time.Date(mondayTime.Year(), mondayTime.Month(), mondayTime.Day(), 0, 0, 0, 0, mondayTime.Location())
+	return mondayTime
+}
+
+// GetNowWeekLastDay 获取本周最后一天的时间
+func GetNowWeekLastDay() time.Time {
+	offset := int(time.Monday - time.Now().Weekday())
+	if offset == 1 { //正好是周日,但是按照中国人的理解,周日是一周最后一天,而不是一周开始的第一天
+		offset = -6
+	}
+	firstDayTime := time.Now().AddDate(0, 0, offset)
+	firstDayTime = time.Date(firstDayTime.Year(), firstDayTime.Month(), firstDayTime.Day(), 0, 0, 0, 0, firstDayTime.Location()).AddDate(0, 0, 6)
+	lastDayTime := time.Date(firstDayTime.Year(), firstDayTime.Month(), firstDayTime.Day(), 23, 59, 59, 0, firstDayTime.Location())
+
+	return lastDayTime
+}
+
+// GetNowMonthFirstDay 获取本月第一天的时间
+func GetNowMonthFirstDay() time.Time {
+	nowMonthFirstDay := time.Date(time.Now().Year(), time.Now().Month(), 1, 0, 0, 0, 0, time.Now().Location())
+	return nowMonthFirstDay
+}
+
+// GetNowMonthLastDay 获取本月最后一天的时间
+func GetNowMonthLastDay() time.Time {
+	nowMonthLastDay := time.Date(time.Now().Year(), time.Now().Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
+	nowMonthLastDay = time.Date(nowMonthLastDay.Year(), nowMonthLastDay.Month(), nowMonthLastDay.Day(), 23, 59, 59, 0, nowMonthLastDay.Location())
+	return nowMonthLastDay
+}
+
+// GetNowQuarterFirstDay 获取本季度第一天的时间
+func GetNowQuarterFirstDay() time.Time {
+	month := int(time.Now().Month())
+	var nowQuarterFirstDay time.Time
+	if month >= 1 && month <= 3 {
+		//1月1号
+		nowQuarterFirstDay = time.Date(time.Now().Year(), 1, 1, 0, 0, 0, 0, time.Now().Location())
+	} else if month >= 4 && month <= 6 {
+		//4月1号
+		nowQuarterFirstDay = time.Date(time.Now().Year(), 4, 1, 0, 0, 0, 0, time.Now().Location())
+	} else if month >= 7 && month <= 9 {
+		nowQuarterFirstDay = time.Date(time.Now().Year(), 7, 1, 0, 0, 0, 0, time.Now().Location())
+	} else {
+		nowQuarterFirstDay = time.Date(time.Now().Year(), 10, 1, 0, 0, 0, 0, time.Now().Location())
+	}
+	return nowQuarterFirstDay
+}
+
+// GetNowQuarterLastDay 获取本季度最后一天的时间
+func GetNowQuarterLastDay() time.Time {
+	month := int(time.Now().Month())
+	var nowQuarterLastDay time.Time
+	if month >= 1 && month <= 3 {
+		//03-31 23:59:59
+		nowQuarterLastDay = time.Date(time.Now().Year(), 3, 31, 23, 59, 59, 0, time.Now().Location())
+	} else if month >= 4 && month <= 6 {
+		//06-30 23:59:59
+		nowQuarterLastDay = time.Date(time.Now().Year(), 6, 30, 23, 59, 59, 0, time.Now().Location())
+	} else if month >= 7 && month <= 9 {
+		//09-30 23:59:59
+		nowQuarterLastDay = time.Date(time.Now().Year(), 9, 30, 23, 59, 59, 0, time.Now().Location())
+	} else {
+		//12-31 23:59:59
+		nowQuarterLastDay = time.Date(time.Now().Year(), 12, 31, 23, 59, 59, 0, time.Now().Location())
+	}
+	return nowQuarterLastDay
+}
+
+// GetNowHalfYearFirstDay 获取当前半年的第一天的时间
+func GetNowHalfYearFirstDay() time.Time {
+	month := int(time.Now().Month())
+	var nowHalfYearLastDay time.Time
+	if month >= 1 && month <= 6 {
+		//03-31 23:59:59
+		nowHalfYearLastDay = time.Date(time.Now().Year(), 1, 1, 0, 0, 0, 0, time.Now().Location())
+	} else {
+		//12-31 23:59:59
+		nowHalfYearLastDay = time.Date(time.Now().Year(), 7, 1, 0, 0, 0, 0, time.Now().Location())
+	}
+	return nowHalfYearLastDay
+}
+
+// GetNowHalfYearLastDay 获取当前半年的最后一天的时间
+func GetNowHalfYearLastDay() time.Time {
+	month := int(time.Now().Month())
+	var nowHalfYearLastDay time.Time
+	if month >= 1 && month <= 6 {
+		//03-31 23:59:59
+		nowHalfYearLastDay = time.Date(time.Now().Year(), 6, 30, 23, 59, 59, 0, time.Now().Location())
+	} else {
+		//12-31 23:59:59
+		nowHalfYearLastDay = time.Date(time.Now().Year(), 12, 31, 23, 59, 59, 0, time.Now().Location())
+	}
+	return nowHalfYearLastDay
+}
+
+// GetNowYearFirstDay 获取当前年的最后一天的时间
+func GetNowYearFirstDay() time.Time {
+	//12-31 23:59:59
+	nowYearFirstDay := time.Date(time.Now().Year(), 1, 1, 0, 0, 0, 0, time.Now().Location())
+	return nowYearFirstDay
+}
+
+// GetNowYearLastDay 获取当前年的最后一天的时间
+func GetNowYearLastDay() time.Time {
+	//12-31 23:59:59
+	nowYearLastDay := time.Date(time.Now().Year(), 12, 31, 23, 59, 59, 0, time.Now().Location())
+	return nowYearLastDay
+}
+
+// CalculationDate 计算两个日期之间相差n年m月y天
+func CalculationDate(startDate, endDate time.Time) (beetweenDay string, err error) {
+	//startDate := time.Date(2021, 3, 28, 0, 0, 0, 0, time.Now().Location())
+	//endDate := time.Date(2022, 3, 31, 0, 0, 0, 0, time.Now().Location())
+	numYear := endDate.Year() - startDate.Year()
+
+	numMonth := int(endDate.Month()) - int(startDate.Month())
+
+	numDay := 0
+	//获取截止月的总天数
+	endDateDays := getMonthDay(endDate.Year(), int(endDate.Month()))
+
+	//获取截止月的前一个月
+	endDatePrevMonthDate := endDate.AddDate(0, -1, 0)
+	//获取截止日期的上一个月的总天数
+	endDatePrevMonthDays := getMonthDay(endDatePrevMonthDate.Year(), int(endDatePrevMonthDate.Month()))
+	//获取开始日期的的月份总天数
+	startDateMonthDays := getMonthDay(startDate.Year(), int(startDate.Month()))
+
+	//判断,截止月是否完全被选中,如果相等,那么代表截止月份全部天数被选择
+	if endDate.Day() == endDateDays {
+		numDay = startDateMonthDays - startDate.Day() + 1
+
+		//如果剩余天数正好与开始日期的天数是一致的,那么月份加1
+		if numDay == startDateMonthDays {
+			numMonth++
+			numDay = 0
+			//超过月份了,那么年份加1
+			if numMonth == 12 {
+				numYear++
+				numMonth = 0
+			}
+		}
+	} else {
+		numDay = endDate.Day() - startDate.Day() + 1
+	}
+
+	//天数小于0,那么向月份借一位
+	if numDay < 0 {
+		//向上一个月借一个月的天数
+		numDay += endDatePrevMonthDays
+
+		//总月份减去一个月
+		numMonth = numMonth - 1
+	}
+
+	//月份小于0,那么向年份借一位
+	if numMonth < 0 {
+		//向上一个年借12个月
+		numMonth += 12
+
+		//总年份减去一年
+		numYear = numYear - 1
+	}
+	if numYear < 0 {
+		err = errors.New("日期异常")
+		return
+	}
+
+	if numYear > 0 {
+		beetweenDay += fmt.Sprint(numYear, "年")
+	}
+	if numMonth > 0 {
+		beetweenDay += fmt.Sprint(numMonth, "个月")
+	}
+	if numDay > 0 {
+		beetweenDay += fmt.Sprint(numDay, "天")
+	}
+	return
+}
+
+// getMonthDay 获取某年某月有多少天
+func getMonthDay(year, month int) (days int) {
+	if month != 2 {
+		if month == 4 || month == 6 || month == 9 || month == 11 {
+			days = 30
+
+		} else {
+			days = 31
+		}
+	} else {
+		if ((year%4) == 0 && (year%100) != 0) || (year%400) == 0 {
+			days = 29
+		} else {
+			days = 28
+		}
+	}
+	return
+}
+
+// InArray 是否在切片(数组/map)中含有该值,目前只支持:string、int 、 int64,其他都是返回false
+func InArray(needle interface{}, hyStack interface{}) bool {
+	switch key := needle.(type) {
+	case string:
+		for _, item := range hyStack.([]string) {
+			if key == item {
+				return true
+			}
+		}
+	case int:
+		for _, item := range hyStack.([]int) {
+			if key == item {
+				return true
+			}
+		}
+	case int64:
+		for _, item := range hyStack.([]int64) {
+			if key == item {
+				return true
+			}
+		}
+	default:
+		return false
+	}
+	return false
+}
+
+// bit转MB 保留小数
+func Bit2MB(bitSize int64, prec int) (size float64) {
+	mb := float64(bitSize) / float64(1024*1024)
+	size, _ = strconv.ParseFloat(strconv.FormatFloat(mb, 'f', prec, 64), 64)
+	return
+}
+
+// SubStr 截取字符串(中文)
+func SubStr(str string, subLen int) string {
+	strRune := []rune(str)
+	bodyRuneLen := len(strRune)
+	if bodyRuneLen > subLen {
+		bodyRuneLen = subLen
+	}
+	str = string(strRune[:bodyRuneLen])
+	return str
+}
+
+func GetInterfaceAddrs() (ip string) {
+	addrs, err := net.InterfaceAddrs()
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	for _, address := range addrs {
+		// 检查ip地址判断是否回环地址
+		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
+			if ipnet.IP.To4() != nil {
+				fmt.Println(ipnet.IP.String())
+				ip = ipnet.IP.String()
+				return
+			}
+		}
+	}
+	return
+}

+ 36 - 0
utils/constants.go

@@ -0,0 +1,36 @@
+package utils
+
+//常量定义
+const (
+	FormatTime            = "15:04:05"                //时间格式
+	FormatDate            = "2006-01-02"              //日期格式
+	FormatDateCN          = "2006年01月02日"             //日期格式(中文)
+	FormatDateUnSpace     = "20060102"                //日期格式
+	FormatDateTime        = "2006-01-02 15:04:05"     //完整时间格式
+	HlbFormatDateTime     = "2006-01-02_15:04:05.999" //完整时间格式
+	FormatDateTimeUnSpace = "20060102150405"          //完整时间格式
+	PageSize15            = 15                        //列表页每页数据量
+	PageSize5             = 5
+	PageSize10            = 10
+	PageSize20            = 20
+	PageSize30            = 30
+)
+
+//手机号,电子邮箱正则
+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 (
+	APPNAME = "弘则财务管理系统"
+)
+
+const DefaultPwd = "9cbf8a4dcb8e30682b927f352d6559a0" //初始密码:123456a
+
+// redis 缓存
+const (
+	SYSTEM_LOGIN_TOKEN          = "fms:login:token:"
+	SYSTEM_LOGIN_TOKEN_NO_TRUST = "fms:login:no_trust:" //管理后台登录(不可信登录态)
+	SYSTEM_LOGIN_ADMINID_IP     = "fms:login:admin_id:"
+)

+ 191 - 0
utils/des3.go

@@ -0,0 +1,191 @@
+//加密工具类,用了3des和base64
+package utils
+
+import (
+	"bytes"
+	"crypto/cipher"
+	"crypto/des"
+	"encoding/base64"
+	"encoding/hex"
+	"errors"
+	"strings"
+)
+
+const (
+	key = ""
+)
+
+//des3 + base64 encrypt
+func DesBase64Encrypt(origData []byte) []byte {
+	result, err := TripleDesEncrypt(origData, []byte(key))
+	if err != nil {
+		panic(err)
+	}
+	return []byte(base64.StdEncoding.EncodeToString(result))
+}
+
+func DesBase64Decrypt(crypted []byte) []byte {
+	result, _ := base64.StdEncoding.DecodeString(string(crypted))
+	remain := len(result) % 8
+	if remain > 0 {
+		mod := 8 - remain
+		for i := 0; i < mod; i++ {
+			result = append(result, 0)
+		}
+	}
+	origData, err := TripleDesDecrypt(result, []byte(key))
+	if err != nil {
+		panic(err)
+	}
+	return origData
+}
+
+// 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
+}
+
+// 3DES解密
+func TripleDesDecrypt(crypted, key []byte) ([]byte, error) {
+	block, err := des.NewTripleDESCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	blockMode := cipher.NewCBCDecrypter(block, key[:8])
+	origData := make([]byte, len(crypted))
+	// origData := crypted
+	blockMode.CryptBlocks(origData, crypted)
+	origData = PKCS5UnPadding(origData)
+	// origData = ZeroUnPadding(origData)
+	return origData, nil
+}
+
+func ZeroPadding(ciphertext []byte, blockSize int) []byte {
+	padding := blockSize - len(ciphertext)%blockSize
+	padtext := bytes.Repeat([]byte{0}, padding)
+	return append(ciphertext, padtext...)
+}
+
+func ZeroUnPadding(origData []byte) []byte {
+	length := len(origData)
+	unpadding := int(origData[length-1])
+	return origData[:(length - unpadding)]
+}
+
+func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
+	padding := blockSize - len(ciphertext)%blockSize
+	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+	return append(ciphertext, padtext...)
+}
+
+func PKCS5UnPadding(origData []byte) []byte {
+	length := len(origData)
+	// 去掉最后一个字节 unpadding 次
+	unpadding := int(origData[length-1])
+	return origData[:(length - unpadding)]
+}
+
+//DES加密
+func DesEncrypt(content string, key string) string {
+	contents := []byte(content)
+	keys := []byte(key)
+	block, err := des.NewCipher(keys)
+	if err != nil {
+		return ""
+	}
+	contents = PKCS5Padding(contents, block.BlockSize())
+	blockMode := cipher.NewCBCEncrypter(block, keys)
+	crypted := make([]byte, len(contents))
+	blockMode.CryptBlocks(crypted, contents)
+	return byteToHexString(crypted)
+}
+
+func byteToHexString(bytes []byte) string {
+	str := ""
+	for i := 0; i < len(bytes); i++ {
+		sTemp := hex.EncodeToString([]byte{bytes[i]})
+		if len(sTemp) < 2 {
+			str += string(0)
+		}
+		str += strings.ToUpper(sTemp)
+	}
+	return str
+}
+
+//DES解密
+func DesDecrypt(content string, key string) string {
+	contentBytes, err := hex.DecodeString(content)
+	if err != nil {
+		return "字符串转换16进制数组失败" + err.Error()
+	}
+	keys := []byte(key)
+	block, err := des.NewCipher(keys)
+	if err != nil {
+		return "解密失败" + err.Error()
+	}
+	blockMode := cipher.NewCBCDecrypter(block, keys)
+	origData := contentBytes
+	blockMode.CryptBlocks(origData, contentBytes)
+	origData = ZeroUnPadding(origData)
+	return string(origData)
+}
+
+// DES ECB PKCK5Padding
+func EntryptDesECB(data, key []byte) (string, error) {
+	if len(key) > 8 {
+		key = key[:8]
+	}
+	block, err := des.NewCipher(key)
+	if err != nil {
+		return "", errors.New("des.NewCipher " + err.Error())
+	}
+	bs := block.BlockSize()
+	data = PKCS5Padding(data, bs)
+	if len(data)%bs != 0 {
+		return "", errors.New("EntryptDesECB Need a multiple of the blocksize")
+	}
+	out := make([]byte, len(data))
+	dst := out
+	for len(data) > 0 {
+		block.Encrypt(dst, data[:bs])
+		data = data[bs:]
+		dst = dst[bs:]
+	}
+	return base64.StdEncoding.EncodeToString(out), nil
+}
+
+func DecryptDESECB(d string, key []byte) ([]byte, error) {
+	data, err := base64.StdEncoding.DecodeString(d)
+	if err != nil {
+		return nil, errors.New("decodebase64 " + err.Error())
+	}
+	if len(key) > 8 {
+		key = key[:8]
+	}
+	block, err := des.NewCipher(key)
+	if err != nil {
+		return nil, errors.New("des.NewCipher " + err.Error())
+	}
+	bs := block.BlockSize()
+	if len(data)%bs != 0 {
+		return nil, errors.New("DecryptDES crypto/cipher: input not full blocks")
+	}
+	out := make([]byte, len(data))
+	dst := out
+	for len(data) > 0 {
+		block.Decrypt(dst, data[:bs])
+		data = data[bs:]
+		dst = dst[bs:]
+	}
+	out = PKCS5UnPadding(out)
+	return out, nil
+}

+ 48 - 0
utils/directory.go

@@ -0,0 +1,48 @@
+package utils
+
+import (
+	"fmt"
+	"os"
+)
+
+// @title    PathExists
+// @description   文件目录是否存在
+// @auth                     (2020/04/05  20:22)
+// @param     path            string
+// @return    err             error
+
+func PathExists(path string) (bool, error) {
+	_, err := os.Stat(path)
+	if err == nil {
+		return true, nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, err
+}
+
+// @title    createDir
+// @description   批量创建文件夹
+// @auth                     (2020/04/05  20:22)
+// @param     dirs            string
+// @return    err             error
+
+func CreateDir(dirs ...string) (err error) {
+	for _, v := range dirs {
+		exist, err := PathExists(v)
+		if err != nil {
+			return err
+		}
+		if !exist {
+			fmt.Println("create directory ", v)
+			//global.LOG.Debug("create directory ", v)
+			err = os.MkdirAll(v, os.ModePerm)
+			if err != nil {
+				fmt.Println("create directory", v, " error:", err)
+				//global.LOG.Error("create directory", v, " error:", err)
+			}
+		}
+	}
+	return err
+}

+ 357 - 0
utils/drawtext.go

@@ -0,0 +1,357 @@
+package utils
+
+import (
+	"bytes"
+	"fmt"
+	"image"
+	"image/color"
+	"image/draw"
+	"image/gif"
+	"image/jpeg"
+	"image/png"
+	"io/ioutil"
+	"net/http"
+	"os"
+
+	"github.com/golang/freetype"
+	"github.com/golang/freetype/truetype"
+	"golang.org/x/image/font"
+)
+
+//DrawTextInfo 图片绘字信息
+type DrawTextInfo struct {
+	Text 		string
+	X    		int
+	Y    		int
+	FontSize	int
+}
+
+//DrawRectInfo 图片画框信息
+type DrawRectInfo struct {
+	X1 int
+	Y1 int
+	X2 int
+	Y2 int
+}
+
+//TextBrush 字体相关
+type TextBrush struct {
+	FontType  *truetype.Font
+	FontSize  float64
+	FontColor *image.Uniform
+	TextWidth int
+}
+
+//NewTextBrush 新生成笔刷
+func NewTextBrush(FontFilePath string, FontSize float64, FontColor *image.Uniform, textWidth int) (*TextBrush, error) {
+	fontFile, err := ioutil.ReadFile(FontFilePath)
+	if err != nil {
+		return nil, err
+	}
+	fontType, err := truetype.Parse(fontFile)
+	if err != nil {
+		return nil, err
+	}
+	if textWidth <= 0 {
+		textWidth = 20
+	}
+	return &TextBrush{FontType: fontType, FontSize: FontSize, FontColor: FontColor, TextWidth: textWidth}, nil
+}
+
+//DrawFontOnRGBA 图片插入文字
+func (fb *TextBrush) DrawFontOnRGBA(rgba *image.RGBA, pt image.Point, content string) {
+
+	c := freetype.NewContext()
+	c.SetDPI(72)
+	c.SetFont(fb.FontType)
+	c.SetHinting(font.HintingFull)
+	c.SetFontSize(fb.FontSize)
+	c.SetClip(rgba.Bounds())
+	c.SetDst(rgba)
+	c.SetSrc(fb.FontColor)
+
+	c.DrawString(content, freetype.Pt(pt.X, pt.Y))
+
+}
+
+//Image2RGBA Image2RGBA
+func Image2RGBA(img image.Image) *image.RGBA {
+
+	baseSrcBounds := img.Bounds().Max
+
+	newWidth := baseSrcBounds.X
+	newHeight := baseSrcBounds.Y
+
+	des := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight)) // 底板
+	//首先将一个图片信息存入jpg
+	draw.Draw(des, des.Bounds(), img, img.Bounds().Min, draw.Over)
+
+	return des
+}
+
+type FontRGBA struct {
+	R uint8
+	G uint8
+	B uint8
+	A uint8
+}
+
+//DrawStringOnImageAndSave 图片上写文字
+func DrawStringOnImageAndSave(imagePath string, imageData []byte, infos []*DrawTextInfo, colorRGBA FontRGBA) (err error) {
+	//判断图片类型
+	var backgroud image.Image
+	filetype := http.DetectContentType(imageData)
+	switch filetype {
+	case "image/jpeg", "image/jpg":
+		backgroud, err = jpeg.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			fmt.Println("jpeg error")
+			return err
+		}
+
+	case "image/gif":
+		backgroud, err = gif.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return err
+		}
+
+	case "image/png":
+		backgroud, err = png.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return err
+		}
+	default:
+		return err
+	}
+	des := Image2RGBA(backgroud)
+
+	//新建笔刷
+	ttfPath := "static/ttf/songti.ttf"
+	textBrush, _ := NewTextBrush(ttfPath, 25, image.Black, 50)
+
+	//Px Py 绘图开始坐标 text要绘制的文字
+	//调整颜色
+	c := freetype.NewContext()
+	c.SetDPI(72)
+	c.SetFont(textBrush.FontType)
+	c.SetHinting(font.HintingFull)
+	c.SetFontSize(textBrush.FontSize)
+	c.SetClip(des.Bounds())
+	c.SetDst(des)
+	textBrush.FontColor = image.NewUniform(color.RGBA{
+		R: colorRGBA.R,
+		G: colorRGBA.G,
+		B: colorRGBA.B,
+		A: colorRGBA.A,
+	})
+	c.SetSrc(textBrush.FontColor)
+
+	for _, info := range infos {
+		c.DrawString(info.Text, freetype.Pt(info.X, info.Y))
+	}
+
+	//保存图片
+	fSave, err := os.Create(imagePath)
+	if err != nil {
+		return err
+	}
+	defer fSave.Close()
+
+	err = jpeg.Encode(fSave, des, nil)
+
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+//DrawRectOnImageAndSave 图片上画多个框
+func DrawRectOnImageAndSave(imagePath string, imageData []byte, infos []*DrawRectInfo) (err error) {
+	//判断图片类型
+	var backgroud image.Image
+	filetype := http.DetectContentType(imageData)
+	switch filetype {
+	case "image/jpeg", "image/jpg":
+		backgroud, err = jpeg.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			fmt.Println("jpeg error")
+			return err
+		}
+
+	case "image/gif":
+		backgroud, err = gif.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return err
+		}
+
+	case "image/png":
+		backgroud, err = png.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return err
+		}
+	default:
+		return err
+	}
+	des := Image2RGBA(backgroud)
+	//新建笔刷
+	textBrush, _ := NewTextBrush("arial.ttf", 15, image.Black, 15)
+	for _, info := range infos {
+		var c *freetype.Context
+		c = freetype.NewContext()
+		c.SetDPI(72)
+		c.SetFont(textBrush.FontType)
+		c.SetHinting(font.HintingFull)
+		c.SetFontSize(textBrush.FontSize)
+		c.SetClip(des.Bounds())
+		c.SetDst(des)
+		cGreen := image.NewUniform(color.RGBA{
+			R: 0,
+			G: 0xFF,
+			B: 0,
+			A: 255,
+		})
+
+		c.SetSrc(cGreen)
+		for i := info.X1; i < info.X2; i++ {
+			c.DrawString("·", freetype.Pt(i, info.Y1))
+			c.DrawString("·", freetype.Pt(i, info.Y2))
+		}
+		for j := info.Y1; j < info.Y2; j++ {
+			c.DrawString("·", freetype.Pt(info.X1, j))
+			c.DrawString("·", freetype.Pt(info.X2, j))
+		}
+	}
+
+	//保存图片
+	fSave, err := os.Create(imagePath)
+	if err != nil {
+		return err
+	}
+	defer fSave.Close()
+
+	err = jpeg.Encode(fSave, des, nil)
+
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+//DrawStringOnImage 生成图片
+func DrawStringOnImage(imageData []byte, infos []*DrawTextInfo, colorRGBA FontRGBA, fontSize float64, fontWidth int) (picBytes bytes.Buffer, err error) {
+	//判断图片类型
+	var backgroud image.Image
+	filetype := http.DetectContentType(imageData)
+	switch filetype {
+	case "image/jpeg", "image/jpg":
+		backgroud, err = jpeg.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			fmt.Println("jpeg error")
+			return
+		}
+
+	case "image/gif":
+		backgroud, err = gif.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return
+		}
+
+	case "image/png":
+		backgroud, err = png.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return
+		}
+	default:
+		return
+	}
+	des := Image2RGBA(backgroud)
+
+	//新建笔刷
+	ttfPath := "static/ttf/songti.ttf"
+	textBrush, _ := NewTextBrush(ttfPath, fontSize, image.Black, fontWidth)
+
+	//Px Py 绘图开始坐标 text要绘制的文字
+	//调整颜色
+	c := freetype.NewContext()
+	c.SetDPI(72)
+	c.SetFont(textBrush.FontType)
+	c.SetHinting(font.HintingFull)
+	c.SetFontSize(textBrush.FontSize)
+	c.SetClip(des.Bounds())
+	c.SetDst(des)
+	textBrush.FontColor = image.NewUniform(color.RGBA{
+		R: colorRGBA.R,
+		G: colorRGBA.G,
+		B: colorRGBA.B,
+		A: colorRGBA.A,
+	})
+	c.SetSrc(textBrush.FontColor)
+
+	for _, info := range infos {
+		c.DrawString(info.Text, freetype.Pt(info.X, info.Y))
+	}
+
+	err = jpeg.Encode(&picBytes, des, nil)
+
+	return
+}
+
+
+// DrawStringRowsOnImage 生成图片
+func DrawStringRowsOnImage(imageData []byte, infos []*DrawTextInfo, colorRGBA FontRGBA, fontSize float64, fontWidth int) (picBytes bytes.Buffer, err error) {
+	//判断图片类型
+	var backgroud image.Image
+	filetype := http.DetectContentType(imageData)
+	switch filetype {
+	case "image/jpeg", "image/jpg":
+		backgroud, err = jpeg.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			fmt.Println("jpeg error")
+			return
+		}
+
+	case "image/gif":
+		backgroud, err = gif.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return
+		}
+
+	case "image/png":
+		backgroud, err = png.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return
+		}
+	default:
+		return
+	}
+	des := Image2RGBA(backgroud)
+
+	//新建笔刷
+	ttfPath := "static/ttf/songti.ttf"
+	textBrush, _ := NewTextBrush(ttfPath, fontSize, image.Black, fontWidth)
+
+	//Px Py 绘图开始坐标 text要绘制的文字
+	//调整颜色
+
+	for _, info := range infos {
+		c := freetype.NewContext()
+		c.SetDPI(72)
+		c.SetFont(textBrush.FontType)
+		c.SetHinting(font.HintingFull)
+		c.SetFontSize(float64(info.FontSize))
+		c.SetClip(des.Bounds())
+		c.SetDst(des)
+		textBrush.FontColor = image.NewUniform(color.RGBA{
+			R: colorRGBA.R,
+			G: colorRGBA.G,
+			B: colorRGBA.B,
+			A: colorRGBA.A,
+		})
+		c.SetSrc(textBrush.FontColor)
+		c.DrawString(info.Text, freetype.Pt(info.X, info.Y))
+	}
+
+	err = jpeg.Encode(&picBytes, des, nil)
+
+	return
+}

+ 214 - 0
utils/validator.go

@@ -0,0 +1,214 @@
+package utils
+
+import (
+	"errors"
+	"reflect"
+	"strconv"
+	"strings"
+)
+
+type Rules map[string][]string
+
+type RulesMap map[string]Rules
+
+var CustomizeMap = make(map[string]Rules)
+
+// 注册自定义规则方案建议在路由初始化层即注册
+func RegisterRule(key string, rule Rules) (err error) {
+	if CustomizeMap[key] != nil {
+		return errors.New(key + "已注册,无法重复注册")
+	} else {
+		CustomizeMap[key] = rule
+		return nil
+	}
+}
+
+// 非空 不能为其对应类型的0值
+func NotEmpty() string {
+	return "notEmpty"
+}
+
+// 小于入参(<) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Lt(mark string) string {
+	return "lt=" + mark
+}
+
+// 小于等于入参(<=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Le(mark string) string {
+	return "le=" + mark
+}
+
+// 等于入参(==) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Eq(mark string) string {
+	return "eq=" + mark
+}
+
+// 不等于入参(!=)  如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Ne(mark string) string {
+	return "ne=" + mark
+}
+
+// 大于等于入参(>=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Ge(mark string) string {
+	return "ge=" + mark
+}
+
+// 大于入参(>) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Gt(mark string) string {
+	return "gt=" + mark
+}
+
+// 校验方法 接收两个参数  入参实例,规则map
+func Verify(st interface{}, roleMap Rules) (err error) {
+	compareMap := map[string]bool{
+		"lt": true,
+		"le": true,
+		"eq": true,
+		"ne": true,
+		"ge": true,
+		"gt": true,
+	}
+
+	typ := reflect.TypeOf(st)
+	val := reflect.ValueOf(st) // 获取reflect.Type类型
+
+	kd := val.Kind() // 获取到st对应的类别
+	if kd != reflect.Struct {
+		return errors.New("expect struct")
+	}
+	num := val.NumField()
+	// 遍历结构体的所有字段
+	for i := 0; i < num; i++ {
+		tagVal := typ.Field(i)
+		val := val.Field(i)
+
+		//如果有填写form字段的话,那么返回提示使用该字段
+		tagName := tagVal.Tag.Get("form")
+		if tagName == "" {
+			tagName = tagVal.Name
+		}
+
+		if len(roleMap[tagVal.Name]) > 0 {
+			for _, v := range roleMap[tagVal.Name] {
+				switch {
+				case v == "notEmpty":
+					if isBlank(val) {
+						return errors.New(tagName + "值不能为空")
+					}
+				case compareMap[strings.Split(v, "=")[0]]:
+					if !compareVerify(val, v) {
+						return errors.New(tagName + "长度或值不在合法范围," + v)
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// 长度和数字的校验方法 根据类型自动校验
+func compareVerify(value reflect.Value, VerifyStr string) bool {
+	switch value.Kind() {
+	case reflect.String, reflect.Slice, reflect.Array:
+		return compare(value.Len(), VerifyStr)
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return compare(value.Uint(), VerifyStr)
+	case reflect.Float32, reflect.Float64:
+		return compare(value.Float(), VerifyStr)
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return compare(value.Int(), VerifyStr)
+	default:
+		return false
+	}
+}
+
+// 非空校验
+func isBlank(value reflect.Value) bool {
+	switch value.Kind() {
+	case reflect.String:
+		return value.Len() == 0
+	case reflect.Bool:
+		return !value.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return value.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return value.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return value.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		return value.IsNil()
+	}
+	return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface())
+}
+
+func compare(value interface{}, VerifyStr string) bool {
+	VerifyStrArr := strings.Split(VerifyStr, "=")
+	val := reflect.ValueOf(value)
+	switch val.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		VInt, VErr := strconv.ParseInt(VerifyStrArr[1], 10, 64)
+		if VErr != nil {
+			return false
+		}
+		switch {
+		case VerifyStrArr[0] == "lt":
+			return val.Int() < VInt
+		case VerifyStrArr[0] == "le":
+			return val.Int() <= VInt
+		case VerifyStrArr[0] == "eq":
+			return val.Int() == VInt
+		case VerifyStrArr[0] == "ne":
+			return val.Int() != VInt
+		case VerifyStrArr[0] == "ge":
+			return val.Int() >= VInt
+		case VerifyStrArr[0] == "gt":
+			return val.Int() > VInt
+		default:
+			return false
+		}
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		VInt, VErr := strconv.Atoi(VerifyStrArr[1])
+		if VErr != nil {
+			return false
+		}
+		switch {
+		case VerifyStrArr[0] == "lt":
+			return val.Uint() < uint64(VInt)
+		case VerifyStrArr[0] == "le":
+			return val.Uint() <= uint64(VInt)
+		case VerifyStrArr[0] == "eq":
+			return val.Uint() == uint64(VInt)
+		case VerifyStrArr[0] == "ne":
+			return val.Uint() != uint64(VInt)
+		case VerifyStrArr[0] == "ge":
+			return val.Uint() >= uint64(VInt)
+		case VerifyStrArr[0] == "gt":
+			return val.Uint() > uint64(VInt)
+		default:
+			return false
+		}
+	case reflect.Float32, reflect.Float64:
+		VFloat, VErr := strconv.ParseFloat(VerifyStrArr[1], 64)
+		if VErr != nil {
+			return false
+		}
+		switch {
+		case VerifyStrArr[0] == "lt":
+			return val.Float() < VFloat
+		case VerifyStrArr[0] == "le":
+			return val.Float() <= VFloat
+		case VerifyStrArr[0] == "eq":
+			return val.Float() == VFloat
+		case VerifyStrArr[0] == "ne":
+			return val.Float() != VFloat
+		case VerifyStrArr[0] == "ge":
+			return val.Float() >= VFloat
+		case VerifyStrArr[0] == "gt":
+			return val.Float() > VFloat
+		default:
+			return false
+		}
+	default:
+		return false
+	}
+}

+ 88 - 0
utils/whereBuild.go

@@ -0,0 +1,88 @@
+package utils
+
+import (
+	"fmt"
+	"strings"
+)
+
+type NullType byte
+
+const (
+	_ NullType = iota
+	// IsNull the same as `is null`
+	IsNull
+	// IsNotNull the same as `is not null`
+	IsNotNull
+)
+
+// sql生成工具
+func WhereBuild(where map[string]interface{}) (whereSQL string, vals []interface{}, err error) {
+	for k, v := range where {
+		ks := strings.Split(k, " ")
+		if len(ks) > 2 {
+			return "", nil, fmt.Errorf("Error in query condition: %s. ", k)
+		}
+
+		if whereSQL != "" {
+			whereSQL += " AND "
+		}
+		strings.Join(ks, ",")
+		switch len(ks) {
+		case 1:
+			//fmt.Println(reflect.TypeOf(v))
+			switch v := v.(type) {
+			case NullType:
+				if v == IsNotNull {
+					whereSQL += fmt.Sprint(k, " IS NOT NULL")
+				} else {
+					whereSQL += fmt.Sprint(k, " IS NULL")
+				}
+			default:
+				whereSQL += fmt.Sprint(k, "=?")
+				vals = append(vals, v)
+			}
+			break
+		case 2:
+			k = ks[0]
+			switch ks[1] {
+			case "=":
+				whereSQL += fmt.Sprint(k, "=?")
+				vals = append(vals, v)
+				break
+			case ">":
+				whereSQL += fmt.Sprint(k, ">?")
+				vals = append(vals, v)
+				break
+			case ">=":
+				whereSQL += fmt.Sprint(k, ">=?")
+				vals = append(vals, v)
+				break
+			case "<":
+				whereSQL += fmt.Sprint(k, "<?")
+				vals = append(vals, v)
+				break
+			case "<=":
+				whereSQL += fmt.Sprint(k, "<=?")
+				vals = append(vals, v)
+				break
+			case "!=":
+				whereSQL += fmt.Sprint(k, "!=?")
+				vals = append(vals, v)
+				break
+			case "<>":
+				whereSQL += fmt.Sprint(k, "!=?")
+				vals = append(vals, v)
+				break
+			case "in":
+				whereSQL += fmt.Sprint(k, " in (?)")
+				vals = append(vals, v)
+				break
+			case "like":
+				whereSQL += fmt.Sprint(k, " like ?")
+				vals = append(vals, v)
+			}
+			break
+		}
+	}
+	return
+}