From 530266720db8a3a108f1fdd89fbe98258ac0e750 Mon Sep 17 00:00:00 2001 From: yxh Date: Wed, 11 Mar 2020 17:58:32 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=97=A5=E5=BF=97=EF=BC=8C=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controller/admin/monitor_login_info.go | 10 - app/controller/admin/monitor_login_log.go | 57 +++ app/controller/admin/monitor_oper_log.go | 69 ++++ .../admin/sys_login_log/sys_login_log.go | 12 + app/model/admin/sys_oper_log/sys_oper_log.go | 162 ++++++++ .../admin/sys_oper_log/sys_oper_log_entity.go | 67 ++++ .../admin/sys_oper_log/sys_oper_log_model.go | 367 ++++++++++++++++++ .../admin/monitor_service/login_log.go | 82 ++++ app/service/admin/monitor_service/online.go | 2 +- app/service/admin/monitor_service/oper_log.go | 35 ++ data/db.sql | 58 ++- hook/hook.go | 34 ++ library/response/response.go | 6 +- library/utils/function.go | 26 +- library/utils/tools.go | 42 ++ middleWare/middleware.go | 4 +- router/router.go | 10 +- 17 files changed, 1008 insertions(+), 35 deletions(-) delete mode 100644 app/controller/admin/monitor_login_info.go create mode 100644 app/controller/admin/monitor_login_log.go create mode 100644 app/controller/admin/monitor_oper_log.go create mode 100644 app/model/admin/sys_oper_log/sys_oper_log.go create mode 100644 app/model/admin/sys_oper_log/sys_oper_log_entity.go create mode 100644 app/model/admin/sys_oper_log/sys_oper_log_model.go create mode 100644 app/service/admin/monitor_service/login_log.go create mode 100644 app/service/admin/monitor_service/oper_log.go create mode 100644 hook/hook.go diff --git a/app/controller/admin/monitor_login_info.go b/app/controller/admin/monitor_login_info.go deleted file mode 100644 index 4101416..0000000 --- a/app/controller/admin/monitor_login_info.go +++ /dev/null @@ -1,10 +0,0 @@ -package admin - -import "github.com/gogf/gf/net/ghttp" - -type MonitorLoginLog struct{} - -//登录日志列表 -func (c *MonitorLoginLog) List(r *ghttp.Request) { - -} diff --git a/app/controller/admin/monitor_login_log.go b/app/controller/admin/monitor_login_log.go new file mode 100644 index 0000000..d9d7c57 --- /dev/null +++ b/app/controller/admin/monitor_login_log.go @@ -0,0 +1,57 @@ +package admin + +import ( + "gfast/app/model/admin/sys_login_log" + "gfast/app/service/admin/dict_service" + "gfast/app/service/admin/monitor_service" + "gfast/library/response" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/util/gvalid" +) + +type MonitorLoginLog struct{} + +//登录日志列表 +func (c *MonitorLoginLog) List(r *ghttp.Request) { + var req *sys_login_log.SelectPageReq + //获取参数 + if err := r.Parse(&req); err != nil { + response.FailJson(true, r, err.(*gvalid.Error).FirstString()) + } + total, page, list, err := monitor_service.LoginLogListByPage(req) + if err != nil { + response.FailJson(true, r, err.Error()) + } + //获取相关选项 + logStatus, err := dict_service.GetDictWithDataByType("admin_login_status", "", "全部") + if err != nil { + response.FailJson(true, r, err.Error()) + } + result := g.Map{ + "currentPage": page, + "total": total, + "list": list, + "searchStatus": logStatus, + } + response.SusJson(true, r, "登录日志列表", result) +} + +//删除登录日志 +func (c *MonitorLoginLog) Delete(r *ghttp.Request) { + ids := r.GetInts("ids") + err := monitor_service.DeleteLoginLogByIds(ids) + if err != nil { + response.FailJson(true, r, err.Error()) + } + response.SusJson(true, r, "删除成功") +} + +//清空登录日志 +func (c *MonitorLoginLog) Clear(r *ghttp.Request) { + err := monitor_service.ClearLoginLog() + if err != nil { + response.FailJson(true, r, err.Error()) + } + response.SusJson(true, r, "清除成功") +} diff --git a/app/controller/admin/monitor_oper_log.go b/app/controller/admin/monitor_oper_log.go new file mode 100644 index 0000000..c9f7f7b --- /dev/null +++ b/app/controller/admin/monitor_oper_log.go @@ -0,0 +1,69 @@ +package admin + +import ( + "gfast/app/model/admin/sys_oper_log" + "gfast/app/service/admin/dict_service" + "gfast/app/service/admin/monitor_service" + "gfast/library/response" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/util/gvalid" +) + +type MonitorOperationLog struct{} + +//操作日志列表 +func (c *MonitorOperationLog) List(r *ghttp.Request) { + var req *sys_oper_log.SelectPageReq + //获取参数 + if err := r.Parse(&req); err != nil { + response.FailJson(true, r, err.(*gvalid.Error).FirstString()) + } + total, page, list, err := monitor_service.OperationLogListByPage(req) + if err != nil { + response.FailJson(true, r, err.Error()) + } + //获取相关选项 + logStatus, err := dict_service.GetDictWithDataByType("sys_oper_log_status", "", "全部") + if err != nil { + response.FailJson(true, r, err.Error()) + } + result := g.Map{ + "currentPage": page, + "total": total, + "list": list, + "searchStatus": logStatus, + } + response.SusJson(true, r, "操作日志列表", result) +} + +//操作详细 +func (c *MonitorOperationLog) Detail(r *ghttp.Request) { + id := r.GetInt64("id") + log, err := monitor_service.GetOperationLogById(id) + if err != nil { + response.FailJson(true, r, err.Error()) + } + response.SusJson(true, r, "操作日志列表", g.Map{ + "info": log, + }) +} + +//删除日志 +func (c *MonitorOperationLog) Delete(r *ghttp.Request) { + ids := r.GetInts("ids") + err := monitor_service.DeleteOperationLogByIds(ids) + if err != nil { + response.FailJson(true, r, err.Error()) + } + response.SusJson(true, r, "删除成功") +} + +//清空日志 +func (c *MonitorOperationLog) Clear(r *ghttp.Request) { + err := monitor_service.ClearOperationLog() + if err != nil { + response.FailJson(true, r, err.Error()) + } + response.SusJson(true, r, "清除成功") +} diff --git a/app/model/admin/sys_login_log/sys_login_log.go b/app/model/admin/sys_login_log/sys_login_log.go index e31eb20..b9d1a16 100644 --- a/app/model/admin/sys_login_log/sys_login_log.go +++ b/app/model/admin/sys_login_log/sys_login_log.go @@ -1,3 +1,15 @@ package sys_login_log // Fill with you ideas below. +//查询列表请求参数 +type SelectPageReq struct { + LoginName string `p:"loginName"` //登陆名 + Status string `p:"status"` //状态 + Ipaddr string `p:"ipaddr"` //登录地址 + BeginTime string `p:"beginTime"` //数据范围 + EndTime string `p:"endTime"` //开始时间 + PageNum int `p:"page"` //当前页码 + PageSize int `p:"pageSize"` //每页数 + SortName string `p:"orderByColumn"` //排序字段 + SortOrder string `p:"isAsc"` //排序方式 +} diff --git a/app/model/admin/sys_oper_log/sys_oper_log.go b/app/model/admin/sys_oper_log/sys_oper_log.go new file mode 100644 index 0000000..d71ebfe --- /dev/null +++ b/app/model/admin/sys_oper_log/sys_oper_log.go @@ -0,0 +1,162 @@ +package sys_oper_log + +import ( + "gfast/app/model/admin/auth_rule" + "gfast/app/model/admin/user" + "gfast/library/utils" + "github.com/gogf/gf/encoding/gjson" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/util/gconv" + "net/url" +) + +// Fill with you ideas below. + +//查询列表请求参数 +type SelectPageReq struct { + Title string `p:"title"` //系统模块 + OperName string `p:"operName"` //操作人员 + Status string `p:"status"` //操作状态 + BeginTime string `p:"beginTime"` //数据范围 + EndTime string `p:"endTime"` //开始时间 + PageNum int `p:"page"` //当前页码 + PageSize int `p:"pageSize"` //每页数 + SortName string `p:"orderByColumn"` //排序字段 + SortOrder string `p:"isAsc"` //排序方式 +} + +//新增操作日志记录 +func Add(user *user.Entity, menu *auth_rule.Entity, url *url.URL, + param g.Map, method, clientIp string) { + var operLog Entity + if menu != nil { + operLog.Title = menu.Title + } + operLog.Method = url.Path + operLog.RequestMethod = method + operLog.OperatorType = 1 + operLog.OperName = user.UserName + rawQuery := url.RawQuery + if rawQuery != "" { + rawQuery = "?" + rawQuery + } + operLog.OperUrl = url.Path + rawQuery + operLog.OperIp = clientIp + operLog.OperLocation = utils.GetCityByIp(operLog.OperIp) + operLog.OperTime = gtime.Timestamp() + if param != nil { + if v, ok := param["apiReturnRes"]; ok { + res := gconv.Map(v) + if gconv.Int(res["code"]) == 0 { + operLog.Status = 1 + } else { + operLog.Status = 0 + } + if _, ok = res["data"]; ok { + delete(res, "data") + } + b, _ := gjson.Encode(res) + if len(b) > 0 { + operLog.JsonResult = string(b) + } + delete(param, "apiReturnRes") + } + b, _ := gjson.Encode(param) + if len(b) > 0 { + operLog.OperParam = string(b) + } + } + operLog.Save() +} + +//操作日志列表 +func ListByPage(req *SelectPageReq) (total, page int, list []*Entity, err error) { + model := Model + order := "oper_time DESC" + if req != nil { + if req.OperName != "" { + model = model.Where("oper_name like ?", "%"+req.OperName+"%") + } + if req.Title != "" { + model = model.Where("title like ?", "%"+req.Title+"%") + } + if req.Status != "" { + model = model.Where("status", gconv.Int(req.Status)) + } + if req.BeginTime != "" { + model = model.Where("oper_time >=", utils.StrToTimestamp(req.BeginTime)) + } + if req.EndTime != "" { + model = model.Where("oper_time <=", utils.StrToTimestamp(req.EndTime)) + } + if req.SortName != "" { + if req.SortOrder != "" { + order = req.SortName + " " + req.SortOrder + } else { + order = req.SortName + " DESC" + } + } + } + total, err = model.Count() + if err != nil { + g.Log().Error(err) + err = gerror.New("获取总行数失败") + return + } + if req.PageNum == 0 { + req.PageNum = 1 + } + page = req.PageNum + if req.PageSize == 0 { + req.PageSize = utils.AdminPageNum + } + list, err = model.Page(page, req.PageSize).FieldsEx("oper_param,json_result").Order(order).All() + if err != nil { + g.Log().Error(err) + err = gerror.New("获取数据失败") + return + } + return +} + +//通过id获取操作日志 +func GetById(id int64) (log *Entity, err error) { + if id == 0 { + err = gerror.New("参数错误") + return + } + log, err = Model.FindOne("oper_id", id) + if err != nil { + g.Log().Error(err) + } + if err != nil || log == nil { + err = gerror.New("获取操作日志失败") + } + return +} + +//删除 +func DeleteByIds(ids []int) (err error) { + if len(ids) == 0 { + err = gerror.New("参数错误") + return + } + _, err = Model.Delete("oper_id in (?)", ids) + if err != nil { + g.Log().Error(err) + err = gerror.New("删除失败") + } + return +} + +//清空 +func ClearLog() (err error) { + _, err = g.DB().Exec("truncate " + Table) + if err != nil { + g.Log().Error(err) + err = gerror.New("清除失败") + } + return +} diff --git a/app/model/admin/sys_oper_log/sys_oper_log_entity.go b/app/model/admin/sys_oper_log/sys_oper_log_entity.go new file mode 100644 index 0000000..9dbe178 --- /dev/null +++ b/app/model/admin/sys_oper_log/sys_oper_log_entity.go @@ -0,0 +1,67 @@ +// ========================================================================== +// This is auto-generated by gf cli tool. You may not really want to edit it. +// ========================================================================== + +package sys_oper_log + +import ( + "database/sql" + "github.com/gogf/gf/database/gdb" +) + +// Entity is the golang structure for table sys_oper_log. +type Entity struct { + OperId int64 `orm:"oper_id,primary" json:"oper_id"` // 日志主键 + Title string `orm:"title" json:"title"` // 模块标题 + BusinessType int `orm:"business_type" json:"business_type"` // 业务类型(0其它 1新增 2修改 3删除) + Method string `orm:"method" json:"method"` // 方法名称 + RequestMethod string `orm:"request_method" json:"request_method"` // 请求方式 + OperatorType int `orm:"operator_type" json:"operator_type"` // 操作类别(0其它 1后台用户 2手机端用户) + OperName string `orm:"oper_name" json:"oper_name"` // 操作人员 + DeptName string `orm:"dept_name" json:"dept_name"` // 部门名称 + OperUrl string `orm:"oper_url" json:"oper_url"` // 请求URL + OperIp string `orm:"oper_ip" json:"oper_ip"` // 主机地址 + OperLocation string `orm:"oper_location" json:"oper_location"` // 操作地点 + OperParam string `orm:"oper_param" json:"oper_param"` // 请求参数 + JsonResult string `orm:"json_result" json:"json_result"` // 返回参数 + Status int `orm:"status" json:"status"` // 操作状态(0正常 1异常) + ErrorMsg string `orm:"error_msg" json:"error_msg"` // 错误消息 + OperTime int64 `orm:"oper_time" json:"oper_time"` // 操作时间 +} + +// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers +// the data and where attributes for empty values. +func (r *Entity) OmitEmpty() *arModel { + return Model.Data(r).OmitEmpty() +} + +// Inserts does "INSERT...INTO..." statement for inserting current object into table. +func (r *Entity) Insert() (result sql.Result, err error) { + return Model.Data(r).Insert() +} + +// Replace does "REPLACE...INTO..." statement for inserting current object into table. +// If there's already another same record in the table (it checks using primary key or unique index), +// it deletes it and insert this one. +func (r *Entity) Replace() (result sql.Result, err error) { + return Model.Data(r).Replace() +} + +// Save does "INSERT...INTO..." statement for inserting/updating current object into table. +// It updates the record if there's already another same record in the table +// (it checks using primary key or unique index). +func (r *Entity) Save() (result sql.Result, err error) { + return Model.Data(r).Save() +} + +// Update does "UPDATE...WHERE..." statement for updating current object from table. +// It updates the record if there's already another same record in the table +// (it checks using primary key or unique index). +func (r *Entity) Update() (result sql.Result, err error) { + return Model.Data(r).Where(gdb.GetWhereConditionOfStruct(r)).Update() +} + +// Delete does "DELETE FROM...WHERE..." statement for deleting current object from table. +func (r *Entity) Delete() (result sql.Result, err error) { + return Model.Where(gdb.GetWhereConditionOfStruct(r)).Delete() +} diff --git a/app/model/admin/sys_oper_log/sys_oper_log_model.go b/app/model/admin/sys_oper_log/sys_oper_log_model.go new file mode 100644 index 0000000..57dd6ef --- /dev/null +++ b/app/model/admin/sys_oper_log/sys_oper_log_model.go @@ -0,0 +1,367 @@ +// ========================================================================== +// This is auto-generated by gf cli tool. You may not really want to edit it. +// ========================================================================== + +package sys_oper_log + +import ( + "database/sql" + "github.com/gogf/gf/database/gdb" + "github.com/gogf/gf/frame/g" + "time" +) + +// arModel is a active record design model for table sys_oper_log operations. +type arModel struct { + M *gdb.Model +} + +var ( + // Table is the table name of sys_oper_log. + Table = "sys_oper_log" + // Model is the model object of sys_oper_log. + Model = &arModel{g.DB("default").Table(Table).Safe()} +) + +// FindOne is a convenience method for Model.FindOne. +// See Model.FindOne. +func FindOne(where ...interface{}) (*Entity, error) { + return Model.FindOne(where...) +} + +// FindAll is a convenience method for Model.FindAll. +// See Model.FindAll. +func FindAll(where ...interface{}) ([]*Entity, error) { + return Model.FindAll(where...) +} + +// FindValue is a convenience method for Model.FindValue. +// See Model.FindValue. +func FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) { + return Model.FindValue(fieldsAndWhere...) +} + +// FindCount is a convenience method for Model.FindCount. +// See Model.FindCount. +func FindCount(where ...interface{}) (int, error) { + return Model.FindCount(where...) +} + +// Insert is a convenience method for Model.Insert. +func Insert(data ...interface{}) (result sql.Result, err error) { + return Model.Insert(data...) +} + +// Replace is a convenience method for Model.Replace. +func Replace(data ...interface{}) (result sql.Result, err error) { + return Model.Replace(data...) +} + +// Save is a convenience method for Model.Save. +func Save(data ...interface{}) (result sql.Result, err error) { + return Model.Save(data...) +} + +// Update is a convenience method for Model.Update. +func Update(dataAndWhere ...interface{}) (result sql.Result, err error) { + return Model.Update(dataAndWhere...) +} + +// Delete is a convenience method for Model.Delete. +func Delete(where ...interface{}) (result sql.Result, err error) { + return Model.Delete(where...) +} + +// As sets an alias name for current table. +func (m *arModel) As(as string) *arModel { + return &arModel{m.M.As(as)} +} + +// TX sets the transaction for current operation. +func (m *arModel) TX(tx *gdb.TX) *arModel { + return &arModel{m.M.TX(tx)} +} + +// Master marks the following operation on master node. +func (m *arModel) Master() *arModel { + return &arModel{m.M.Master()} +} + +// Slave marks the following operation on slave node. +// Note that it makes sense only if there's any slave node configured. +func (m *arModel) Slave() *arModel { + return &arModel{m.M.Slave()} +} + +// LeftJoin does "LEFT JOIN ... ON ..." statement on the model. +func (m *arModel) LeftJoin(joinTable string, on string) *arModel { + return &arModel{m.M.LeftJoin(joinTable, on)} +} + +// RightJoin does "RIGHT JOIN ... ON ..." statement on the model. +func (m *arModel) RightJoin(joinTable string, on string) *arModel { + return &arModel{m.M.RightJoin(joinTable, on)} +} + +// InnerJoin does "INNER JOIN ... ON ..." statement on the model. +func (m *arModel) InnerJoin(joinTable string, on string) *arModel { + return &arModel{m.M.InnerJoin(joinTable, on)} +} + +// Fields sets the operation fields of the model, multiple fields joined using char ','. +func (m *arModel) Fields(fields string) *arModel { + return &arModel{m.M.Fields(fields)} +} + +// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','. +func (m *arModel) FieldsEx(fields string) *arModel { + return &arModel{m.M.FieldsEx(fields)} +} + +// Option sets the extra operation option for the model. +func (m *arModel) Option(option int) *arModel { + return &arModel{m.M.Option(option)} +} + +// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers +// the data and where attributes for empty values. +func (m *arModel) OmitEmpty() *arModel { + return &arModel{m.M.OmitEmpty()} +} + +// Filter marks filtering the fields which does not exist in the fields of the operated table. +func (m *arModel) Filter() *arModel { + return &arModel{m.M.Filter()} +} + +// Where sets the condition statement for the model. The parameter can be type of +// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, +// multiple conditions will be joined into where statement using "AND". +// Eg: +// Where("uid=10000") +// Where("uid", 10000) +// Where("money>? AND name like ?", 99999, "vip_%") +// Where("uid", 1).Where("name", "john") +// Where("status IN (?)", g.Slice{1,2,3}) +// Where("age IN(?,?)", 18, 50) +// Where(User{ Id : 1, UserName : "john"}) +func (m *arModel) Where(where interface{}, args ...interface{}) *arModel { + return &arModel{m.M.Where(where, args...)} +} + +// And adds "AND" condition to the where statement. +func (m *arModel) And(where interface{}, args ...interface{}) *arModel { + return &arModel{m.M.And(where, args...)} +} + +// Or adds "OR" condition to the where statement. +func (m *arModel) Or(where interface{}, args ...interface{}) *arModel { + return &arModel{m.M.Or(where, args...)} +} + +// Group sets the "GROUP BY" statement for the model. +func (m *arModel) Group(groupBy string) *arModel { + return &arModel{m.M.Group(groupBy)} +} + +// Order sets the "ORDER BY" statement for the model. +func (m *arModel) Order(orderBy string) *arModel { + return &arModel{m.M.Order(orderBy)} +} + +// Limit sets the "LIMIT" statement for the model. +// The parameter can be either one or two number, if passed two number is passed, +// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]" +// statement. +func (m *arModel) Limit(limit ...int) *arModel { + return &arModel{m.M.Limit(limit...)} +} + +// Offset sets the "OFFSET" statement for the model. +// It only makes sense for some databases like SQLServer, PostgreSQL, etc. +func (m *arModel) Offset(offset int) *arModel { + return &arModel{m.M.Offset(offset)} +} + +// Page sets the paging number for the model. +// The parameter is started from 1 for paging. +// Note that, it differs that the Limit function start from 0 for "LIMIT" statement. +func (m *arModel) Page(page, limit int) *arModel { + return &arModel{m.M.Page(page, limit)} +} + +// Batch sets the batch operation number for the model. +func (m *arModel) Batch(batch int) *arModel { + return &arModel{m.M.Batch(batch)} +} + +// Cache sets the cache feature for the model. It caches the result of the sql, which means +// if there's another same sql request, it just reads and returns the result from cache, it +// but not committed and executed into the database. +// +// If the parameter < 0, which means it clear the cache with given . +// If the parameter = 0, which means it never expires. +// If the parameter > 0, which means it expires after . +// +// The optional parameter is used to bind a name to the cache, which means you can later +// control the cache like changing the or clearing the cache with specified . +// +// Note that, the cache feature is disabled if the model is operating on a transaction. +func (m *arModel) Cache(expire time.Duration, name ...string) *arModel { + return &arModel{m.M.Cache(expire, name...)} +} + +// Data sets the operation data for the model. +// The parameter can be type of string/map/gmap/slice/struct/*struct, etc. +// Eg: +// Data("uid=10000") +// Data("uid", 10000) +// Data(g.Map{"uid": 10000, "name":"john"}) +// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) +func (m *arModel) Data(data ...interface{}) *arModel { + return &arModel{m.M.Data(data...)} +} + +// Insert does "INSERT INTO ..." statement for the model. +// The optional parameter is the same as the parameter of Model.Data function, +// see Model.Data. +func (m *arModel) Insert(data ...interface{}) (result sql.Result, err error) { + return m.M.Insert(data...) +} + +// Replace does "REPLACE INTO ..." statement for the model. +// The optional parameter is the same as the parameter of Model.Data function, +// see Model.Data. +func (m *arModel) Replace(data ...interface{}) (result sql.Result, err error) { + return m.M.Replace(data...) +} + +// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model. +// It updates the record if there's primary or unique index in the saving data, +// or else it inserts a new record into the table. +// +// The optional parameter is the same as the parameter of Model.Data function, +// see Model.Data. +func (m *arModel) Save(data ...interface{}) (result sql.Result, err error) { + return m.M.Save(data...) +} + +// Update does "UPDATE ... " statement for the model. +// +// If the optional parameter is given, the dataAndWhere[0] is the updated +// data field, and dataAndWhere[1:] is treated as where condition fields. +// Also see Model.Data and Model.Where functions. +func (m *arModel) Update(dataAndWhere ...interface{}) (result sql.Result, err error) { + return m.M.Update(dataAndWhere...) +} + +// Delete does "DELETE FROM ... " statement for the model. +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +func (m *arModel) Delete(where ...interface{}) (result sql.Result, err error) { + return m.M.Delete(where...) +} + +// Count does "SELECT COUNT(x) FROM ..." statement for the model. +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +func (m *arModel) Count(where ...interface{}) (int, error) { + return m.M.Count(where...) +} + +// All does "SELECT FROM ..." statement for the model. +// It retrieves the records from table and returns the result as []*Entity. +// It returns nil if there's no record retrieved with the given conditions from table. +// +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +func (m *arModel) All(where ...interface{}) ([]*Entity, error) { + all, err := m.M.All(where...) + if err != nil { + return nil, err + } + var entities []*Entity + if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows { + return nil, err + } + return entities, nil +} + +// One retrieves one record from table and returns the result as *Entity. +// It returns nil if there's no record retrieved with the given conditions from table. +// +// The optional parameter is the same as the parameter of Model.Where function, +// see Model.Where. +func (m *arModel) One(where ...interface{}) (*Entity, error) { + one, err := m.M.One(where...) + if err != nil { + return nil, err + } + var entity *Entity + if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows { + return nil, err + } + return entity, nil +} + +// Value retrieves a specified record value from table and returns the result as interface type. +// It returns nil if there's no record found with the given conditions from table. +// +// If the optional parameter is given, the fieldsAndWhere[0] is the selected fields +// and fieldsAndWhere[1:] is treated as where condition fields. +// Also see Model.Fields and Model.Where functions. +func (m *arModel) Value(fieldsAndWhere ...interface{}) (gdb.Value, error) { + return m.M.Value(fieldsAndWhere...) +} + +// FindOne retrieves and returns a single Record by Model.WherePri and Model.One. +// Also see Model.WherePri and Model.One. +func (m *arModel) FindOne(where ...interface{}) (*Entity, error) { + one, err := m.M.FindOne(where...) + if err != nil { + return nil, err + } + var entity *Entity + if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows { + return nil, err + } + return entity, nil +} + +// FindAll retrieves and returns Result by by Model.WherePri and Model.All. +// Also see Model.WherePri and Model.All. +func (m *arModel) FindAll(where ...interface{}) ([]*Entity, error) { + all, err := m.M.FindAll(where...) + if err != nil { + return nil, err + } + var entities []*Entity + if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows { + return nil, err + } + return entities, nil +} + +// FindValue retrieves and returns single field value by Model.WherePri and Model.Value. +// Also see Model.WherePri and Model.Value. +func (m *arModel) FindValue(fieldsAndWhere ...interface{}) (gdb.Value, error) { + return m.M.FindValue(fieldsAndWhere...) +} + +// FindCount retrieves and returns the record number by Model.WherePri and Model.Count. +// Also see Model.WherePri and Model.Count. +func (m *arModel) FindCount(where ...interface{}) (int, error) { + return m.M.FindCount(where...) +} + +// Chunk iterates the table with given size and callback function. +func (m *arModel) Chunk(limit int, callback func(entities []*Entity, err error) bool) { + m.M.Chunk(limit, func(result gdb.Result, err error) bool { + var entities []*Entity + err = result.Structs(&entities) + if err == sql.ErrNoRows { + return false + } + return callback(entities, err) + }) +} diff --git a/app/service/admin/monitor_service/login_log.go b/app/service/admin/monitor_service/login_log.go new file mode 100644 index 0000000..d23ed06 --- /dev/null +++ b/app/service/admin/monitor_service/login_log.go @@ -0,0 +1,82 @@ +package monitor_service + +import ( + "gfast/app/model/admin/sys_login_log" + "gfast/library/utils" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gconv" +) + +func LoginLogListByPage(req *sys_login_log.SelectPageReq) (total, page int, list []*sys_login_log.Entity, err error) { + model := sys_login_log.Model + order := "login_time DESC" + if req != nil { + if req.LoginName != "" { + model = model.Where("login_name like ?", "%"+req.LoginName+"%") + } + if req.Status != "" { + model = model.Where("status", gconv.Int(req.Status)) + } + if req.Ipaddr != "" { + model = model.Where("ipaddr like ?", "%"+req.Ipaddr+"%") + } + if req.BeginTime != "" { + model = model.Where("login_time >=", utils.StrToTimestamp(req.BeginTime)) + } + if req.EndTime != "" { + model = model.Where("login_time <=", utils.StrToTimestamp(req.EndTime)) + } + if req.SortName != "" { + if req.SortOrder != "" { + order = req.SortName + " " + req.SortOrder + } else { + order = req.SortName + " DESC" + } + } + } + total, err = model.Count() + if err != nil { + g.Log().Error(err) + err = gerror.New("获取总行数失败") + return + } + if req.PageNum == 0 { + req.PageNum = 1 + } + page = req.PageNum + if req.PageSize == 0 { + req.PageSize = utils.AdminPageNum + } + list, err = model.Page(page, req.PageSize).Order(order).All() + if err != nil { + g.Log().Error(err) + err = gerror.New("获取数据失败") + return + } + return +} + +//删除登录日志 +func DeleteLoginLogByIds(ids []int) (err error) { + if len(ids) == 0 { + err = gerror.New("参数错误") + return + } + _, err = sys_login_log.Model.Delete("info_id in (?)", ids) + if err != nil { + g.Log().Error(err) + err = gerror.New("删除失败") + } + return +} + +//清空日志 +func ClearLoginLog() (err error) { + _, err = g.DB().Exec("truncate " + sys_login_log.Table) + if err != nil { + g.Log().Error(err) + err = gerror.New("清除失败") + } + return +} diff --git a/app/service/admin/monitor_service/online.go b/app/service/admin/monitor_service/online.go index d423d12..3543893 100644 --- a/app/service/admin/monitor_service/online.go +++ b/app/service/admin/monitor_service/online.go @@ -20,7 +20,7 @@ func GetOnlineListPage(req *user_online.ReqListSearch) (total, page int, list [] model = model.Where("ip like ?", "%"+req.Ip+"%") } if req.Username != "" { - model = model.Where("username like ?", "%"+req.Username+"%") + model = model.Where("user_name like ?", "%"+req.Username+"%") } } total, err = model.Count() diff --git a/app/service/admin/monitor_service/oper_log.go b/app/service/admin/monitor_service/oper_log.go new file mode 100644 index 0000000..337c007 --- /dev/null +++ b/app/service/admin/monitor_service/oper_log.go @@ -0,0 +1,35 @@ +package monitor_service + +import ( + "gfast/app/model/admin/auth_rule" + "gfast/app/model/admin/sys_oper_log" + "gfast/app/model/admin/user" + "github.com/gogf/gf/frame/g" + "net/url" +) + +//新增操作日志记录 +func OperationLogAdd(user *user.Entity, menu *auth_rule.Entity, url *url.URL, + param g.Map, method, clientIp string) { + sys_oper_log.Add(user, menu, url, param, method, clientIp) +} + +//操作日志列表 +func OperationLogListByPage(req *sys_oper_log.SelectPageReq) (total, page int, list []*sys_oper_log.Entity, err error) { + return sys_oper_log.ListByPage(req) +} + +//通过id获取操作日志 +func GetOperationLogById(id int64) (log *sys_oper_log.Entity, err error) { + return sys_oper_log.GetById(id) +} + +//批量删除 +func DeleteOperationLogByIds(ids []int) (err error) { + return sys_oper_log.DeleteByIds(ids) +} + +//清空 +func ClearOperationLog() (err error) { + return sys_oper_log.ClearLog() +} diff --git a/data/db.sql b/data/db.sql index a323d82..29df353 100644 --- a/data/db.sql +++ b/data/db.sql @@ -40,7 +40,7 @@ CREATE TABLE `auth_rule` ( UNIQUE KEY `name` (`name`) USING BTREE, KEY `pid` (`pid`), KEY `weigh` (`weigh`) -) ENGINE=MyISAM AUTO_INCREMENT=69 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='菜单节点表'; +) ENGINE=MyISAM AUTO_INCREMENT=83 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='菜单节点表'; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -49,7 +49,7 @@ CREATE TABLE `auth_rule` ( LOCK TABLES `auth_rule` WRITE; /*!40000 ALTER TABLE `auth_rule` DISABLE KEYS */; -INSERT INTO `auth_rule` VALUES (5,'file',0,'/system/auth','权限管理','fa fa-users','','',1,1497429920,1582706271,80,1),(9,'file',5,'/system/auth/user-list','管理员管理','fa fa-user','','Admin tips',1,1497429920,1497430320,118,1),(10,'file',5,'/system/auth/adminlog','管理员日志','fa fa-list-alt','','Admin log tips',1,1497429920,1582731276,113,1),(11,'file',5,'/system/auth/role-list','角色组','fa fa-group','','Group tips',1,1497429920,1582706904,109,1),(12,'file',5,'/system/auth/menu-list','菜单规则','fa fa-bars','','Rule tips',1,1497429920,1582731459,104,1),(43,'file',9,'/system/auth/delete-admin','删除','fa fa-circle-o','','',0,1497429920,1582706783,1,1),(42,'file',9,'/system/auth/edit-user','修改','fa fa-circle-o','','',0,1497429920,1582706817,2,1),(41,'file',9,'/system/auth/add-user','添加','fa fa-circle-o','','',0,1497429920,1582726029,3,1),(47,'file',11,'/system/auth/add-role','添加角色','el-icon-document-add','','',0,1582706959,1582706959,50,1),(50,'file',12,'/system/auth/add-menu','添加菜单','el-icon-document-add','','',0,1582726105,1582726105,50,1),(46,'file',0,'/system/index/index','系统首页','el-icon-s-grid','noCheck','',1,1582704520,1582726295,100,1),(48,'file',11,'/system/auth/edit-role','修改角色','fa fa-pencil','','',0,1582706999,1582726035,50,1),(49,'file',11,'/system/auth/delete-role','删除角色','fa fa-institution','','',0,1582707051,1582726040,50,1),(51,'file',12,'/system/auth/edit-menu','修改菜单','fa fa-pencil','','',0,1582726145,1582726145,50,1),(52,'file',12,'/system/auth/delete-menu','删除菜单','fa fa-institution','','',0,1582726194,1582726194,50,1),(53,'file',12,'/system/auth/menu-sort','菜单排序','fa fa-list-ul','','',0,1582726229,1582726229,50,1),(57,'file',0,'/system/cms','CMS管理','fa fa-list','','',1,1582731636,1582731636,70,1),(58,'file',57,'/system/cms/menu-list','栏目管理','fa fa-list-ul','','',1,1582731762,1582732995,50,1),(59,'file',57,'/system/cms/news-list','内容管理','fa fa-th-list','','',1,1582731800,1582733003,50,1),(60,'file',57,'/system/cms/mode-list','模型管理','fa fa-codepen','','',1,1582731832,1582733011,50,1),(61,'file',57,'/system/cms/special-list','专题管理','fa fa-hand-pointer-o','','',1,1582732334,1582733018,50,1),(62,'file',57,'/system/cms/single-list','单页管理','fa fa-file-o','','',1,1582732373,1582733025,50,1),(63,'file',0,'/system/config','系统配置','fa fa-wrench','','',1,1582773590,1582773600,90,1),(64,'file',63,'/system/config/dict/list','字典管理','fa fa-sitemap','','',1,1582773640,1583457951,50,1),(65,'file',63,'/system/config/params/list','参数管理','fa fa-list-ul','','',1,1582773725,1583457960,50,1),(66,'file',0,'/system/monitor','系统监控','fa fa-video-camera','','',1,1583745179,1583745179,75,1),(67,'file',66,'/system/monitor/online/list','在线用户','fa fa-list-ul','','',1,1583745243,1583745243,50,1),(68,'file',67,'/system/monitor/online/force-logout','强退用户','fa fa-chevron-right','','',0,1583745300,1583745300,50,1); +INSERT INTO `auth_rule` VALUES (5,'file',0,'/system/auth','权限管理','fa fa-users','','',1,1497429920,1582706271,80,1),(9,'file',5,'/system/auth/user-list','管理员管理','fa fa-user','','Admin tips',1,1497429920,1497430320,118,1),(10,'file',5,'/system/auth/adminlog','管理员日志','fa fa-list-alt','','Admin log tips',1,1497429920,1582731276,113,1),(11,'file',5,'/system/auth/role-list','角色组','fa fa-group','','Group tips',1,1497429920,1582706904,109,1),(12,'file',5,'/system/auth/menu-list','菜单规则','fa fa-bars','','Rule tips',1,1497429920,1582731459,104,1),(43,'file',9,'/system/auth/delete-admin','删除','fa fa-circle-o','','',0,1497429920,1582706783,1,1),(42,'file',9,'/system/auth/edit-user','修改','fa fa-circle-o','','',0,1497429920,1582706817,2,1),(41,'file',9,'/system/auth/add-user','添加','fa fa-circle-o','','',0,1497429920,1582726029,3,1),(47,'file',11,'/system/auth/add-role','添加角色','el-icon-document-add','','',0,1582706959,1582706959,50,1),(50,'file',12,'/system/auth/add-menu','添加菜单','el-icon-document-add','','',0,1582726105,1582726105,50,1),(46,'file',0,'/system/index/index','系统首页','el-icon-s-grid','noCheck','',1,1582704520,1582726295,100,1),(48,'file',11,'/system/auth/edit-role','修改角色','fa fa-pencil','','',0,1582706999,1582726035,50,1),(49,'file',11,'/system/auth/delete-role','删除角色','fa fa-institution','','',0,1582707051,1582726040,50,1),(51,'file',12,'/system/auth/edit-menu','修改菜单','fa fa-pencil','','',0,1582726145,1582726145,50,1),(52,'file',12,'/system/auth/delete-menu','删除菜单','fa fa-institution','','',0,1582726194,1582726194,50,1),(53,'file',12,'/system/auth/menu-sort','菜单排序','fa fa-list-ul','','',0,1582726229,1582726229,50,1),(57,'file',0,'/system/cms','CMS管理','fa fa-list','','',1,1582731636,1582731636,70,1),(58,'file',57,'/system/cms/menu-list','栏目管理','fa fa-list-ul','','',1,1582731762,1582732995,50,1),(59,'file',57,'/system/cms/news-list','内容管理','fa fa-th-list','','',1,1582731800,1582733003,50,1),(60,'file',57,'/system/cms/mode-list','模型管理','fa fa-codepen','','',1,1582731832,1582733011,50,1),(61,'file',57,'/system/cms/special-list','专题管理','fa fa-hand-pointer-o','','',1,1582732334,1582733018,50,1),(62,'file',57,'/system/cms/single-list','单页管理','fa fa-file-o','','',1,1582732373,1582733025,50,1),(63,'file',0,'/system/config','系统配置','fa fa-wrench','','',1,1582773590,1582773600,90,1),(64,'file',63,'/system/config/dict/list','字典管理','fa fa-sitemap','','',1,1582773640,1583457951,50,1),(65,'file',63,'/system/config/params/list','参数管理','fa fa-list-ul','','',1,1582773725,1583457960,50,1),(66,'file',0,'/system/monitor','系统监控','fa fa-video-camera','','',1,1583745179,1583745179,75,1),(67,'file',66,'/system/monitor/online/list','在线用户','fa fa-list-ul','','',1,1583745243,1583745243,50,1),(68,'file',67,'/system/monitor/online/force-logout','强退用户','fa fa-chevron-right','','',0,1583745300,1583745300,50,1),(69,'file',66,'/system/monitor/job/list','定时任务','fa fa-caret-square-o-right','','',1,1583910055,1583910055,50,1),(70,'file',69,'/system/monitor/job/add','添加任务','fa fa-plus','','',0,1583910115,1583910115,50,1),(71,'file',69,'/system/monitor/job/edit','修改任务','fa fa-pencil','','',0,1583910178,1583910178,50,1),(72,'file',69,'/system/monitor/job/start','启动任务','fa fa-play','','',0,1583910216,1583910216,50,1),(73,'file',69,'/system/monitor/job/stop','停止任务','fa fa-square','','',0,1583910258,1583910258,50,1),(74,'file',69,'/system/monitor/job/delete','删除任务','fa fa-institution','','',0,1583910310,1583910310,50,1),(75,'file',66,'/system/monitor/server/info','服务监控','el-icon-video-camera-solid','','',1,1583912579,1583912579,50,1),(76,'file',66,'/system/monitor/loginlog/list','登录日志','fa fa-stack-exchange','','',1,1583912674,1583912674,50,1),(77,'file',76,'/system/monitor/loginlog/delete','删除登录日志','fa fa-trash-o','','',0,1583912733,1583912733,50,1),(78,'file',76,'/system/monitor/loginlog/clear','清空登录日志','el-icon-close','','',0,1583912761,1583912761,50,1),(79,'file',66,'/system/monitor/operlog/list','操作日志','el-icon-finished','','',1,1583913013,1583913013,50,1),(80,'file',79,'/system/monitor/operlog/detail','操作日志详情','fa fa-list-ul','','',0,1583920512,1583920512,50,1),(81,'file',79,'/system/monitor/operlog/delete','删除操作日志','fa fa-trash-o','','',0,1583920563,1583920563,50,1),(82,'file',79,'/system/monitor/operlog/clear','清空操作日志','fa fa-university','','',0,1583920599,1583920599,50,1); /*!40000 ALTER TABLE `auth_rule` ENABLE KEYS */; UNLOCK TABLES; @@ -234,7 +234,7 @@ CREATE TABLE `sys_dict_data` ( `update_time` bigint(20) unsigned DEFAULT '0' COMMENT '更新时间', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`dict_code`) -) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4 COMMENT='字典数据表'; +) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8mb4 COMMENT='字典数据表'; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -243,7 +243,7 @@ CREATE TABLE `sys_dict_data` ( LOCK TABLES `sys_dict_data` WRITE; /*!40000 ALTER TABLE `sys_dict_data` DISABLE KEYS */; -INSERT INTO `sys_dict_data` VALUES (1,0,'男','0','sys_user_sex','','default',0,1,31,1582884113,31,1583763043,'备注信息'),(2,0,'女','0','sys_user_sex','','default',0,1,31,1582884125,31,1583763046,'备注信息'),(3,0,'保密','0','sys_user_sex','','default',1,1,31,1582884871,31,1583763050,'备注信息'),(24,0,'作为频道页,不可作为栏目发布文章,可添加下级分类','1','cms_category_type','','default',1,1,31,1583131942,0,0,'作为频道页,不可作为栏目发布文章,可添加下级分类'),(25,0,'作为发布栏目,可添加文章','2','cms_category_type','','default',0,1,31,1583132032,0,0,'作为发布栏目,可添加文章'),(26,0,'不直接发布内容,用于跳转页面','3','cms_category_type','','default',0,1,31,1583132125,0,0,'不直接发布内容,用于跳转页面'),(27,0,'单页面模式,分类直接显示为文章','4','cms_category_type','','default',0,1,31,1583132145,0,0,'单页面模式,分类直接显示为文章'),(28,0,'正常','0','sys_job_status','','default',1,1,31,1583762727,0,0,''),(29,0,'暂停','1','sys_job_status','','default',0,1,31,1583762751,31,1583763095,''),(30,0,'默认','DEFAULT','sys_job_group','','default',1,1,31,1583762854,0,0,''),(31,0,'系统','SYSTEM','sys_job_group','','default',0,1,31,1583762889,0,0,''); +INSERT INTO `sys_dict_data` VALUES (1,0,'男','0','sys_user_sex','','default',0,1,31,1582884113,31,1583763043,'备注信息'),(2,0,'女','0','sys_user_sex','','default',0,1,31,1582884125,31,1583763046,'备注信息'),(3,0,'保密','0','sys_user_sex','','default',1,1,31,1582884871,31,1583763050,'备注信息'),(24,0,'作为频道页,不可作为栏目发布文章,可添加下级分类','1','cms_category_type','','default',1,1,31,1583131942,0,0,'作为频道页,不可作为栏目发布文章,可添加下级分类'),(25,0,'作为发布栏目,可添加文章','2','cms_category_type','','default',0,1,31,1583132032,0,0,'作为发布栏目,可添加文章'),(26,0,'不直接发布内容,用于跳转页面','3','cms_category_type','','default',0,1,31,1583132125,0,0,'不直接发布内容,用于跳转页面'),(27,0,'单页面模式,分类直接显示为文章','4','cms_category_type','','default',0,1,31,1583132145,0,0,'单页面模式,分类直接显示为文章'),(28,0,'正常','0','sys_job_status','','default',1,1,31,1583762727,0,0,''),(29,0,'暂停','1','sys_job_status','','default',0,1,31,1583762751,31,1583763095,''),(30,0,'默认','DEFAULT','sys_job_group','','default',1,1,31,1583762854,0,0,''),(31,0,'系统','SYSTEM','sys_job_group','','default',0,1,31,1583762889,0,0,''),(32,0,'成功','1','admin_login_status','','default',0,1,31,1583891238,31,1583891244,''),(33,0,'失败','0','admin_login_status','','default',0,1,31,1583891262,0,0,''),(34,0,'成功','1','sys_oper_log_status','','default',0,1,31,1583917929,0,0,''),(35,0,'失败','0','sys_oper_log_status','','default',0,1,31,1583917942,0,0,''); /*!40000 ALTER TABLE `sys_dict_data` ENABLE KEYS */; UNLOCK TABLES; @@ -266,7 +266,7 @@ CREATE TABLE `sys_dict_type` ( `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`dict_id`), UNIQUE KEY `dict_type` (`dict_type`) -) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COMMENT='字典类型表'; +) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COMMENT='字典类型表'; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -275,7 +275,7 @@ CREATE TABLE `sys_dict_type` ( LOCK TABLES `sys_dict_type` WRITE; /*!40000 ALTER TABLE `sys_dict_type` DISABLE KEYS */; -INSERT INTO `sys_dict_type` VALUES (1,'用户性别','sys_user_sex',1,31,1582789177,0,0,'用于选择用户性别'),(2,'分类类型','cms_category_type',1,31,1582789253,31,1583131577,'文章分类类型'),(3,'任务状态','sys_job_status',1,31,1582789296,31,1583762542,'任务状态列表'),(13,'任务分组','sys_job_group',1,31,1583762785,0,0,''); +INSERT INTO `sys_dict_type` VALUES (1,'用户性别','sys_user_sex',1,31,1582789177,0,0,'用于选择用户性别'),(2,'分类类型','cms_category_type',1,31,1582789253,31,1583131577,'文章分类类型'),(3,'任务状态','sys_job_status',1,31,1582789296,31,1583762542,'任务状态列表'),(13,'任务分组','sys_job_group',1,31,1583762785,0,0,''),(14,'管理员登录状态','admin_login_status',1,31,1583891209,0,0,''),(15,'操作日志状态','sys_oper_log_status',1,31,1583917888,0,0,''); /*!40000 ALTER TABLE `sys_dict_type` ENABLE KEYS */; UNLOCK TABLES; @@ -345,6 +345,44 @@ LOCK TABLES `sys_login_log` WRITE; /*!40000 ALTER TABLE `sys_login_log` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Table structure for table `sys_oper_log` +-- + +DROP TABLE IF EXISTS `sys_oper_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `sys_oper_log` ( + `oper_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键', + `title` varchar(50) DEFAULT '' COMMENT '模块标题', + `business_type` int(2) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)', + `method` varchar(100) DEFAULT '' COMMENT '方法名称', + `request_method` varchar(10) DEFAULT '' COMMENT '请求方式', + `operator_type` int(1) DEFAULT '0' COMMENT '操作类别(0其它 1后台用户 2手机端用户)', + `oper_name` varchar(50) DEFAULT '' COMMENT '操作人员', + `dept_name` varchar(50) DEFAULT '' COMMENT '部门名称', + `oper_url` varchar(255) DEFAULT '' COMMENT '请求URL', + `oper_ip` varchar(50) DEFAULT '' COMMENT '主机地址', + `oper_location` varchar(255) DEFAULT '' COMMENT '操作地点', + `oper_param` text COMMENT '请求参数', + `json_result` text COMMENT '返回参数', + `status` int(1) DEFAULT '0' COMMENT '操作状态(0正常 1异常)', + `error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息', + `oper_time` bigint(20) DEFAULT '0' COMMENT '操作时间', + PRIMARY KEY (`oper_id`) +) ENGINE=MyISAM AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COMMENT='操作日志记录'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_oper_log` +-- + +LOCK TABLES `sys_oper_log` WRITE; +/*!40000 ALTER TABLE `sys_oper_log` DISABLE KEYS */; +INSERT INTO `sys_oper_log` VALUES (1,'',0,'/system/monitor/operlog/clear','POST',1,'admin','','/system/monitor/operlog/clear','192.168.0.212','','{}','{\"code\":0,\"msg\":\"清除成功\"}',1,'',1583920443),(2,'菜单规则',0,'/system/auth/menu-list','GET',1,'demo','','/system/auth/menu-list','192.168.0.212','','{}','{\"code\":0,\"msg\":\"成功\"}',1,'',1583920465),(3,'添加菜单',0,'/system/auth/add-menu','GET',1,'demo','','/system/auth/add-menu','192.168.0.212','','{}','{\"code\":0,\"msg\":\"成功\"}',1,'',1583920465),(4,'添加菜单',0,'/system/auth/add-menu','POST',1,'demo','','/system/auth/add-menu','192.168.0.212','','{\"condition\":\"\",\"icon\":\"fa fa-list-ul\",\"ismenu\":0,\"name\":\"/system/monitor/operlog/detail\",\"pid\":79,\"remark\":\"\",\"status\":1,\"title\":\"操作日志详情\",\"weigh\":50}','{\"code\":0,\"msg\":\"添加菜单成功\"}',1,'',1583920513),(5,'菜单规则',0,'/system/auth/menu-list','GET',1,'demo','','/system/auth/menu-list','192.168.0.212','','{}','{\"code\":0,\"msg\":\"成功\"}',1,'',1583920513),(6,'添加菜单',0,'/system/auth/add-menu','GET',1,'demo','','/system/auth/add-menu','192.168.0.212','','{}','{\"code\":0,\"msg\":\"成功\"}',1,'',1583920513),(7,'添加菜单',0,'/system/auth/add-menu','POST',1,'demo','','/system/auth/add-menu','192.168.0.212','','{\"condition\":\"\",\"icon\":\"fa fa-trash-o\",\"ismenu\":0,\"name\":\"/system/monitor/operlog/delete\",\"pid\":79,\"remark\":\"\",\"status\":1,\"title\":\"删除操作日志\",\"weigh\":50}','{\"code\":0,\"msg\":\"添加菜单成功\"}',1,'',1583920563),(8,'菜单规则',0,'/system/auth/menu-list','GET',1,'demo','','/system/auth/menu-list','192.168.0.212','','{}','{\"code\":0,\"msg\":\"成功\"}',1,'',1583920563),(9,'添加菜单',0,'/system/auth/add-menu','GET',1,'demo','','/system/auth/add-menu','192.168.0.212','','{}','{\"code\":0,\"msg\":\"成功\"}',1,'',1583920563),(10,'添加菜单',0,'/system/auth/add-menu','POST',1,'demo','','/system/auth/add-menu','192.168.0.212','','{\"condition\":\"\",\"icon\":\"fa fa-university\",\"ismenu\":0,\"name\":\"/system/monitor/operlog/clear\",\"pid\":79,\"remark\":\"\",\"status\":1,\"title\":\"清空操作日志\",\"weigh\":50}','{\"code\":0,\"msg\":\"添加菜单成功\"}',1,'',1583920600),(11,'添加菜单',0,'/system/auth/add-menu','GET',1,'demo','','/system/auth/add-menu','192.168.0.212','','{}','{\"code\":0,\"msg\":\"成功\"}',1,'',1583920600),(12,'菜单规则',0,'/system/auth/menu-list','GET',1,'demo','','/system/auth/menu-list','192.168.0.212','','{}','{\"code\":0,\"msg\":\"成功\"}',1,'',1583920600); +/*!40000 ALTER TABLE `sys_oper_log` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `user` -- @@ -379,7 +417,7 @@ CREATE TABLE `user` ( LOCK TABLES `user` WRITE; /*!40000 ALTER TABLE `user` DISABLE KEYS */; -INSERT INTO `user` VALUES (1,'admin','18687460581','超级管理员',0,1557715675,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'yxh669@qq.com',0,'',1583744648,'[::1]'),(2,'yixiaohu','13699885599','易小虎',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'yxh@qq.com',1,'',1583479226,'[::1]'),(3,'zs','16399669855','张三',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'zs@qq.com',0,'',1559293160,'127.0.0.1'),(4,'qlgl','13758596696','测试c',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'qlgl@qq.com',0,'',1559293134,'127.0.0.1'),(5,'test','13845696696','测试2',0,0,'9OFlt5qzzvCiZWhe7ilcLA==',1,'123@qq.com',0,'',0,''),(6,'18999998889','13755866654','刘大大',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'1223@qq.com',0,'',0,''),(7,'zmm','13788566696','张明明',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'11123@qq.com',0,'',0,''),(8,'lxx','13756566696','李小小',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'123333@qq.com',0,'',1571729563,'127.0.0.1'),(10,'xmm','13588999969','小秘密',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(14,'cd_19','123154564','看金利科技',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(15,'lmm','135877545454','刘敏敏',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(16,'ldn','13899658874','李大牛',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(20,'dbc','13877555566','大百词',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(22,'yxfmlbb','15969423326','袁学飞',0,1557715675,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'yxh6691@qq.com',0,'',1565059554,'127.0.0.1'),(23,'wangming','13699888855','王明',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(24,'zhk','13699885591','综合科',0,0,'IZNU7Pn91/++830Pi6HAWA==',1,'',0,'',1569288069,'192.168.0.146'),(28,'demo3','18699888855','测试账号1',0,1581314035,'9OFlt5qzzvCiZWhe7ilcLA==',1,'',0,'',0,''),(31,'demo','18699888856','测试账号1',0,1581314770,'9OFlt5qzzvCiZWhe7ilcLA==',1,'56@qq.com',0,'',1583762268,'192.168.31.221'),(32,'demo100','18699888859','测试账号1',0,1582103659,'9OFlt5qzzvCiZWhe7ilcLA==',1,'',0,'',0,''),(33,'demo110','18699888853','测试账号1',0,1582109337,'9OFlt5qzzvCiZWhe7ilcLA==',1,'',0,'',0,''),(34,'demo101','13855774455','测试账号1',0,1582110232,'9OFlt5qzzvCiZWhe7ilcLA==',1,'',0,'',0,''),(38,'demo103','18699888833','测试账号103',0,1582188923,'9OFlt5qzzvCiZWhe7ilcLA==',1,'',0,'',1582188938,'[::1]'); +INSERT INTO `user` VALUES (1,'admin','18687460581','超级管理员',0,1557715675,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'yxh669@qq.com',0,'',1583894086,'192.168.0.212'),(2,'yixiaohu','13699885599','易小虎',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'yxh@qq.com',1,'',1583479226,'[::1]'),(3,'zs','16399669855','张三',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'zs@qq.com',0,'',1559293160,'127.0.0.1'),(4,'qlgl','13758596696','测试c',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'qlgl@qq.com',0,'',1559293134,'127.0.0.1'),(5,'test','13845696696','测试2',0,0,'9OFlt5qzzvCiZWhe7ilcLA==',1,'123@qq.com',0,'',0,''),(6,'18999998889','13755866654','刘大大',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'1223@qq.com',0,'',0,''),(7,'zmm','13788566696','张明明',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'11123@qq.com',0,'',0,''),(8,'lxx','13756566696','李小小',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'123333@qq.com',0,'',1571729563,'127.0.0.1'),(10,'xmm','13588999969','小秘密',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(14,'cd_19','123154564','看金利科技',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(15,'lmm','135877545454','刘敏敏',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(16,'ldn','13899658874','李大牛',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(20,'dbc','13877555566','大百词',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(22,'yxfmlbb','15969423326','袁学飞',0,1557715675,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'yxh6691@qq.com',0,'',1565059554,'127.0.0.1'),(23,'wangming','13699888855','王明',0,0,'IJ1xz+Wve+ZONVMFfXJQMw==',1,'',0,'',0,''),(24,'zhk','13699885591','综合科',0,0,'IZNU7Pn91/++830Pi6HAWA==',1,'',0,'',1569288069,'192.168.0.146'),(28,'demo3','18699888855','测试账号1',0,1581314035,'9OFlt5qzzvCiZWhe7ilcLA==',1,'',0,'',0,''),(31,'demo','18699888856','测试账号1',0,1581314770,'9OFlt5qzzvCiZWhe7ilcLA==',1,'56@qq.com',0,'',1583894004,'192.168.0.212'),(32,'demo100','18699888859','测试账号1',0,1582103659,'9OFlt5qzzvCiZWhe7ilcLA==',1,'',0,'',0,''),(33,'demo110','18699888853','测试账号1',0,1582109337,'9OFlt5qzzvCiZWhe7ilcLA==',1,'',0,'',0,''),(34,'demo101','13855774455','测试账号1',0,1582110232,'9OFlt5qzzvCiZWhe7ilcLA==',1,'',0,'',0,''),(38,'demo103','18699888833','测试账号103',0,1582188923,'9OFlt5qzzvCiZWhe7ilcLA==',1,'',0,'',1582188938,'[::1]'); /*!40000 ALTER TABLE `user` ENABLE KEYS */; UNLOCK TABLES; @@ -401,7 +439,7 @@ CREATE TABLE `user_online` ( `os` varchar(30) NOT NULL DEFAULT '' COMMENT '操作系统', PRIMARY KEY (`id`), UNIQUE KEY `uni_token` (`token`) USING BTREE -) ENGINE=MyISAM AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COMMENT='用户在线状态表'; +) ENGINE=MyISAM AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COMMENT='用户在线状态表'; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -410,7 +448,7 @@ CREATE TABLE `user_online` ( LOCK TABLES `user_online` WRITE; /*!40000 ALTER TABLE `user_online` DISABLE KEYS */; -INSERT INTO `user_online` VALUES (20,'8adc454b79ee32c3d541dd3f1741aff8','fCLUxFpxP3QqVAezWSiqJ8OtDAKMoyVj/rWfOL9EvCCwCUvixJbwCD5Ukvqulmm7//dgfo2xvENltgSUQzsMjDh8mJUfquABLuMvcdRrDXsGLkaeS/8JPNqO7sd1HCQY',1583762268,'demo','192.168.31.221','Chrome','Windows 10'); +INSERT INTO `user_online` VALUES (20,'8adc454b79ee32c3d541dd3f1741aff8','fCLUxFpxP3QqVAezWSiqJ8OtDAKMoyVj/rWfOL9EvCCwCUvixJbwCD5Ukvqulmm7//dgfo2xvENltgSUQzsMjDh8mJUfquABLuMvcdRrDXsGLkaeS/8JPNqO7sd1HCQY',1583762268,'demo','192.168.31.221','Chrome','Windows 10'),(29,'70965993534b37da57ba93e952a8f7b7','lpe0YG9uqSN+sc8zL1tF+9Wcdo668H3tXJxqoKs0sIpULsmmu8SNCn4Y7lyrWhX9MxGyhMP0ksnUkaC1HHCvEPkUIHqjarQw70N/9E8CebISLbUvwzZTcQygi/ruaJIa',1583894060,'admin','[::1]','PostmanRuntime',''),(28,'eeaf0b8a58270d48100ec1b01876115c','fCLUxFpxP3QqVAezWSiqJ7D3QYZ7jkylPshqw8BVzfJJfAHAbwsSFhjrTyqHZEnzZwFT0YRIfumQTxnJAjCUHKpnrXWMqRHjXbV4yPu0tn7dHESK+gRr5ODilRyUkc5h',1583894004,'demo','192.168.0.212','Chrome','Windows 10'),(30,'d3cb2f2bc7846aa0b962366bc67bb234','lpe0YG9uqSN+sc8zL1tF+0bVNmHSnBiJshz6cFTW0bO1YDzPEgiMyIy2o0szcsqq89te5+yx+6Pz8G3gLrpKoSR7JEPD8YXqGwV3JOmcA23bjYRWtWoXlfQ51WSVEVgb',1583894086,'admin','192.168.0.212','PostmanRuntime',''); /*!40000 ALTER TABLE `user_online` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; @@ -423,4 +461,4 @@ UNLOCK TABLES; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-03-10 18:03:43 +-- Dump completed on 2020-03-11 17:57:36 diff --git a/hook/hook.go b/hook/hook.go new file mode 100644 index 0000000..005d093 --- /dev/null +++ b/hook/hook.go @@ -0,0 +1,34 @@ +package hook + +import ( + "gfast/app/model/admin/auth_rule" + "gfast/app/service/admin/auth_service" + "gfast/app/service/admin/monitor_service" + "gfast/app/service/admin/user_service" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/text/gstr" +) + +func OperationLog(r *ghttp.Request) { + user := user_service.GetLoginAdminInfo(r) + if user == nil { + return + } + url := r.Request.URL //请求地址 + //获取菜单 + //获取地址对应的菜单id + menuList, err := auth_service.GetMenuList() + if err != nil { + g.Log().Error(err) + return + } + var menu *auth_rule.Entity + for _, m := range menuList { + if gstr.Equal(m.Name, url.Path) { + menu = m + break + } + } + go monitor_service.OperationLogAdd(user, menu, url, r.GetMap(), r.Method, r.GetClientIp()) +} diff --git a/library/response/response.go b/library/response/response.go index 2c080a4..cd78b9e 100644 --- a/library/response/response.go +++ b/library/response/response.go @@ -26,11 +26,13 @@ func RJson(r *ghttp.Request, code int, msg string, data ...interface{}) { if len(data) > 0 { responseData = data[0] } - r.Response.WriteJson(g.Map{ + result := g.Map{ "code": code, "msg": msg, "data": responseData, - }) + } + r.SetParam("apiReturnRes", result) + r.Response.WriteJson(result) } //成功返回JSON diff --git a/library/utils/function.go b/library/utils/function.go index acabc40..477a64b 100644 --- a/library/utils/function.go +++ b/library/utils/function.go @@ -3,6 +3,7 @@ package utils import ( "database/sql" "errors" + "gfast/app/model/admin/sys_login_log" "gfast/app/model/admin/user" "gfast/app/model/admin/user_online" "gfast/library/response" @@ -95,10 +96,14 @@ func AdminLogin(r *ghttp.Request) (string, interface{}) { } else { keys = data["username"] + password } + ip := r.GetClientIp() + userAgent := r.Header.Get("User-Agent") if err, user := signIn(data["username"], password, r); err != nil { + go loginLog(0, data["username"], ip, userAgent, err.Error()) response.JsonExit(r, response.ErrorCode, err.Error()) } else { r.SetParam("userInfo", user) + go loginLog(1, data["username"], ip, userAgent, "登录成功") return keys, user } return keys, nil @@ -215,12 +220,17 @@ func signIn(username, password string, r *ghttp.Request) (error, *user.User) { return nil, &returnData } -//日期字符串转时间戳(秒) -func StrToTimestamp(dateStr string) int64 { - tm, err := gtime.StrToTime(dateStr) - if err != nil { - g.Log().Error(err) - return 0 - } - return tm.Timestamp() +//登录日志记录 +func loginLog(status int, username, ip, userAgent, msg string) { + var log sys_login_log.Entity + log.LoginName = username + log.Ipaddr = ip + log.LoginLocation = GetCityByIp(log.Ipaddr) + ua := user_agent.New(userAgent) + log.Browser, _ = ua.Browser() + log.Os = ua.OS() + log.Status = status + log.Msg = msg + log.LoginTime = gtime.Timestamp() + log.Save() } diff --git a/library/utils/tools.go b/library/utils/tools.go index ee858ff..96213de 100644 --- a/library/utils/tools.go +++ b/library/utils/tools.go @@ -1,6 +1,11 @@ package utils import ( + "github.com/gogf/gf/encoding/gcharset" + "github.com/gogf/gf/encoding/gjson" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/gtime" "net" "time" ) @@ -40,3 +45,40 @@ func GetHourDiffer(startTime, endTime string) int64 { return hour } } + +//日期字符串转时间戳(秒) +func StrToTimestamp(dateStr string) int64 { + tm, err := gtime.StrToTime(dateStr) + if err != nil { + g.Log().Error(err) + return 0 + } + return tm.Timestamp() +} + +//获取ip所属城市 +func GetCityByIp(ip string) string { + if ip == "" { + return "" + } + + if ip == "[::1]" || ip == "127.0.0.1" { + return "内网IP" + } + + url := "http://whois.pconline.com.cn/ipJson.jsp?json=true&ip=" + ip + bytes := ghttp.GetBytes(url) + src := string(bytes) + srcCharset := "GBK" + tmp, _ := gcharset.ToUTF8(srcCharset, src) + json, err := gjson.DecodeToJson(tmp) + if err != nil { + return "" + } + if json.GetInt("code") == 0 { + city := json.GetString("city") + return city + } else { + return "" + } +} diff --git a/middleWare/middleware.go b/middleWare/middleware.go index db2e946..769e670 100644 --- a/middleWare/middleware.go +++ b/middleWare/middleware.go @@ -14,13 +14,13 @@ import ( ) //跨域处理中间件 -func MiddlewareCORS(r *ghttp.Request) { +func CORS(r *ghttp.Request) { r.Response.CORSDefault() r.Middleware.Next() } //权限判断处理中间件 -func MiddlewareAuth(r *ghttp.Request) { +func Auth(r *ghttp.Request) { //获取登陆用户id adminId := user_service.GetLoginID(r) //获取无需验证权限的用户id diff --git a/router/router.go b/router/router.go index baed703..cb1e162 100644 --- a/router/router.go +++ b/router/router.go @@ -2,6 +2,7 @@ package router import ( "gfast/app/controller/admin" + "gfast/hook" "gfast/middleWare" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/net/ghttp" @@ -10,13 +11,16 @@ import ( // 统一路由注册. func init() { s := g.Server() - s.Use(middleWare.MiddlewareCORS) + s.Use(middleWare.CORS) group := s.Group("/") group.Group("/sysLogin", func(group *ghttp.RouterGroup) { group.ALL("/public", new(admin.Public)) }) group.Group("/system", func(group *ghttp.RouterGroup) { - group.Middleware(middleWare.MiddlewareAuth) //后台权限验证 + group.Middleware(middleWare.Auth) //后台权限验证 + //后台操作日志记录 + group.Hook("/*", ghttp.HOOK_AFTER_OUTPUT, hook.OperationLog) + group.ALL("/index", new(admin.Index)) group.ALL("/auth", new(admin.Auth)) @@ -34,6 +38,8 @@ func init() { group.ALL("/online", new(admin.MonitorOnline)) group.ALL("/job", new(admin.MonitorJob)) group.ALL("/server", new(admin.MonitorServer)) + group.ALL("/operlog", new(admin.MonitorOperationLog)) + group.ALL("/loginlog", new(admin.MonitorLoginLog)) }) })