Merge pull request #62 from bufanyun/v2.0

up
This commit is contained in:
maxbad 2024-05-09 19:57:33 +08:00 committed by GitHub
commit 406e3ef168
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
63 changed files with 672 additions and 478 deletions

View File

@ -153,7 +153,7 @@ func (s *sSysTable) Edit(ctx context.Context, in *sysin.TableEditInp) (err error
// 新增
in.CreatedBy = contexts.GetUserId(ctx)
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Data(in).Insert(); err != nil {
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, "新增表格失败,请稍后重试!")
return
}

View File

@ -125,7 +125,7 @@ func (s *sSysTreeTable) Edit(ctx context.Context, in *sysin.TableEditInp) (err e
}
} else {
in.CreatedBy = contexts.GetUserId(ctx)
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Data(in).Insert(); err != nil {
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, "新增表格失败,请稍后重试!")
return err
}

View File

@ -30,16 +30,6 @@ type ExportReq struct {
type ExportRes struct{}
// ViewReq 获取登录日志指定信息
type ViewReq struct {
g.Meta `path:"/loginLog/view" method:"get" tags:"登录日志" summary:"获取登录日志指定信息"`
sysin.LoginLogViewInp
}
type ViewRes struct {
*sysin.LoginLogViewModel
}
// DeleteReq 删除登录日志
type DeleteReq struct {
g.Meta `path:"/loginLog/delete" method:"post" tags:"登录日志" summary:"删除登录日志"`

View File

@ -5,9 +5,56 @@
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package consts
import (
"fmt"
"github.com/gogf/gf/v2/errors/gcode"
"hotgo/internal/library/dict"
"hotgo/internal/model"
"net/http"
)
func init() {
dict.RegisterEnums("HTTPMethod", "HTTP请求方式选项", HTTPMethodOptions)
dict.RegisterEnums("HTTPHandlerTime", "HTTP处理耗时选项", HTTPHandlerTimeOptions)
dict.RegisterEnums("HTTPApiCode", "HTTP接口状态码选项", HTTPApiCodeOptions)
}
const (
HTTPContentTypeXml = "text/xml"
HTTPContentTypeHtml = "text/html"
HTTPContentTypeStream = "text/event-stream"
HTTPContentTypeJson = "application/json"
)
// HTTPMethodOptions HTTP请求方式选项
var HTTPMethodOptions = []*model.Option{
dict.GenSuccessOption(http.MethodGet, "GET"),
dict.GenInfoOption(http.MethodPost, "POST"),
dict.GenSuccessOption(http.MethodPut, "PUT"),
dict.GenInfoOption(http.MethodDelete, "DELETE"),
}
const (
HTTPHandlerTime50 = "< 50"
HTTPHandlerTime200 = "< 200"
HTTPHandlerTime200To500 = "BETWEEN 200 AND 500"
HTTPHandlerTime500To1000 = "BETWEEN 500 AND 1000"
HTTPHandlerTime1000To10000 = "BETWEEN 1000 AND 10000"
HTTPHandlerTime10000UP = "> 10000"
)
// HTTPHandlerTimeOptions HTTP处理耗时选项
var HTTPHandlerTimeOptions = []*model.Option{
dict.GenSuccessOption(HTTPHandlerTime50, "50ms以内"),
dict.GenInfoOption(HTTPHandlerTime200, "200ms以内"),
dict.GenSuccessOption(HTTPHandlerTime200To500, "200~500ms"),
dict.GenSuccessOption(HTTPHandlerTime500To1000, "500~1000ms"),
dict.GenInfoOption(HTTPHandlerTime1000To10000, "1000~10000ms"),
dict.GenInfoOption(HTTPHandlerTime10000UP, "10000ms以上"),
}
// HTTPApiCodeOptions HTTP接口状态码选项
var HTTPApiCodeOptions = []*model.Option{
dict.GenSuccessOption(gcode.CodeOK.Code(), fmt.Sprintf("%v %v", gcode.CodeOK.Code(), "成功")),
dict.GenWarningOption(gcode.CodeNil.Code(), fmt.Sprintf("%v %v", gcode.CodeNil.Code(), "失败")),
}

View File

@ -38,18 +38,6 @@ func (c *cLoginLog) Export(ctx context.Context, req *loginlog.ExportReq) (res *l
return
}
// View 获取指定登录日志信息
func (c *cLoginLog) View(ctx context.Context, req *loginlog.ViewReq) (res *loginlog.ViewRes, err error) {
data, err := service.SysLoginLog().View(ctx, &req.LoginLogViewInp)
if err != nil {
return
}
res = new(loginlog.ViewRes)
res.LoginLogViewModel = data
return
}
// Delete 删除登录日志
func (c *cLoginLog) Delete(ctx context.Context, req *loginlog.DeleteReq) (res *loginlog.DeleteRes, err error) {
err = service.SysLoginLog().Delete(ctx, &req.LoginLogDeleteInp)

View File

@ -0,0 +1,125 @@
// Package global
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2024 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package global
import (
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gmeta"
"github.com/gogf/gf/v2/util/gtag"
"reflect"
"strings"
"sync"
)
// HTTPRouter http路由
type HTTPRouter struct {
ghttp.RouterItem
Tags string `json:"tags" dc:"接口所属的标签,用于接口分类"`
Summary string `json:"summary" dc:"接口/参数概要描述"`
Description string `json:"description" dc:"接口/参数详细描述"`
}
var (
httpRoutes map[string]*HTTPRouter
routeMutex sync.Mutex
shortTypeMapForTag = map[string]string{
gtag.SummaryShort: gtag.Summary,
gtag.SummaryShort2: gtag.Summary,
gtag.DescriptionShort: gtag.Description,
gtag.DescriptionShort2: gtag.Description,
}
)
// GetRequestRoute 获取当前请求路由属性
func GetRequestRoute(r *ghttp.Request) *HTTPRouter {
key := GenFilterRequestKey(r)
routes := LoadHTTPRoutes(r)
router, ok := routes[key]
if !ok {
return nil
}
return router
}
// GenFilterRequestKey 根据请求生成唯一key
func GenFilterRequestKey(r *ghttp.Request) string {
return GenRouteKey(r.Method, r.Request.URL.Path)
}
// GenFilterRouteKey 根据路由生成唯一key
func GenFilterRouteKey(r *ghttp.Router) string {
return GenRouteKey(r.Method, r.Uri)
}
// GenRouteKey 生成唯一key
func GenRouteKey(method, path string) string {
return strings.ToUpper(method) + " " + path
}
func LoadHTTPRoutes(r *ghttp.Request) map[string]*HTTPRouter {
if httpRoutes == nil {
routeMutex.Lock()
defer routeMutex.Unlock()
if httpRoutes != nil {
return httpRoutes
}
httpRoutes = make(map[string]*HTTPRouter, len(r.Server.GetRoutes()))
for _, v := range r.Server.GetRoutes() {
key := GenFilterRouteKey(v.Handler.Router)
if _, ok := httpRoutes[key]; !ok {
router := new(HTTPRouter)
router.RouterItem = v
httpRoutes[key] = setRouterMeta(router)
}
}
}
return httpRoutes
}
func setRouterMeta(router *HTTPRouter) *HTTPRouter {
if !router.RouterItem.Handler.Info.IsStrictRoute {
return router
}
var reflectValue = reflect.ValueOf(router.Handler.Info.Value.Interface())
for reflectValue.Kind() == reflect.Ptr {
reflectValue = reflectValue.Elem()
}
if reflectValue.Kind() != reflect.Func {
return router
}
var reflectType = reflect.TypeOf(router.Handler.Info.Value.Interface())
if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 {
return router
}
var inputObject reflect.Value
if reflectType.In(1).Kind() == reflect.Ptr {
inputObject = reflect.New(reflectType.In(1).Elem()).Elem()
} else {
inputObject = reflect.New(reflectType.In(1)).Elem()
}
inputMetaMap := fillMapWithShortTags(gmeta.Data(inputObject.Interface()))
router.Tags = inputMetaMap["tags"]
router.Summary = inputMetaMap[gtag.Summary]
router.Description = inputMetaMap[gtag.Description]
return router
}
func fillMapWithShortTags(m map[string]string) map[string]string {
for k, v := range shortTypeMapForTag {
if m[v] == "" && m[k] != "" {
m[v] = m[k]
}
}
return m
}

View File

@ -65,7 +65,7 @@ func Install(m Module) (err error) {
_, _ = GetModel(m.Ctx()).Where("id", record.Id).Delete()
}
if _, err = GetModel(m.Ctx()).Data(data).Insert(); err != nil {
if _, err = GetModel(m.Ctx()).Data(data).OmitEmptyData().Insert(); err != nil {
return err
}
return m.Install(ctx)

View File

@ -151,7 +151,7 @@ func (a *adapter) SavePolicy(model model.Model) (err error) {
}
if count := len(policyRules); count > 0 {
if _, err = a.model().Insert(policyRules); err != nil {
if _, err = a.model().OmitEmptyData().Insert(policyRules); err != nil {
return
}
}
@ -160,7 +160,7 @@ func (a *adapter) SavePolicy(model model.Model) (err error) {
// AddPolicy adds a policy rule to the storage.
func (a *adapter) AddPolicy(sec string, ptype string, rule []string) (err error) {
_, err = a.model().Insert(a.buildPolicyRule(ptype, rule))
_, err = a.model().OmitEmptyData().Insert(a.buildPolicyRule(ptype, rule))
return
}
@ -176,7 +176,7 @@ func (a *adapter) AddPolicies(sec string, ptype string, rules [][]string) (err e
policyRules = append(policyRules, a.buildPolicyRule(ptype, rule))
}
_, err = a.model().Insert(policyRules)
_, err = a.model().OmitEmptyData().Insert(policyRules)
return
}

View File

@ -75,8 +75,10 @@ func GenHashOption(key interface{}, label string) *model.Option {
tag := "default"
if _, err := hash.Write(gconv.Bytes(label)); err == nil {
index := int(hash.Sum32()) % len(strings)
if index < len(strings) {
tag = strings[index]
}
}
return &model.Option{
Key: key,
Label: label,

View File

@ -19,7 +19,7 @@ const (
LogicWhereComments = "\n\t// 查询%s\n"
LogicWhereNoSupport = "\t// TODO 暂不支持生成[ %s ]查询方式,请自行补充此处代码!"
LogicEditUpdate = "\tif _, err = s.Model(ctx%s).\n\t\t\tFields(%sin.%sUpdateFields{}).\n\t\t\tWherePri(in.%s).Data(in).Update(); err != nil {\n\t\t\terr = gerror.Wrap(err, \"修改%s失败请稍后重试\")\n\t\t}\n\t\treturn"
LogicEditInsert = "\tif _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).\n\t\tFields(%sin.%sInsertFields{}).\n\t\tData(in).Insert(); err != nil {\n\t\terr = gerror.Wrap(err, \"新增%s失败请稍后重试\")\n\t}"
LogicEditInsert = "\tif _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).\n\t\tFields(%sin.%sInsertFields{}).\n\t\tData(in).OmitEmptyData().Insert(); err != nil {\n\t\terr = gerror.Wrap(err, \"新增%s失败请稍后重试\")\n\t}"
LogicEditUnique = "\t// 验证'%s'唯一\n\tif err = hgorm.IsUnique(ctx, &dao.%s, g.Map{dao.%s.Columns().%s: in.%s}, \"%s已存在\", in.Id); err != nil {\n\t\treturn\n\t}\n"
LogicSwitchUpdate = "g.Map{\n\t\tin.Key: in.Value,\n%s}"
LogicStatusUpdate = "g.Map{\n\t\tdao.%s.Columns().Status: in.Status,\n%s}"

View File

@ -50,7 +50,7 @@ func TestSaveTenant_User(t *testing.T) {
cols.Status: consts.PayStatusWait,
}
_, err := dao.AddonHgexampleTenantOrder.Ctx(ctx).Data(data).Hook(hook.SaveTenant).Insert()
_, err := dao.AddonHgexampleTenantOrder.Ctx(ctx).Data(data).Hook(hook.SaveTenant).OmitEmptyData().Insert()
if err != nil {
panic(err)
}

View File

@ -89,7 +89,7 @@ func (m *MsgParser) RegisterRPCRouter(routers ...interface{}) (err error) {
// RegisterInterceptor 注册拦截器
func (m *MsgParser) RegisterInterceptor(interceptors ...Interceptor) {
m.interceptors = append(interceptors, interceptors...)
m.interceptors = append(m.interceptors, interceptors...)
}
// Encoding 消息编码

View File

@ -233,7 +233,7 @@ func write(ctx context.Context, meta *FileMeta, fullPath string) (models *entity
Status: consts.StatusEnabled,
}
id, err := GetModel(ctx).Data(models).InsertAndGetId()
id, err := GetModel(ctx).Data(models).OmitEmptyData().InsertAndGetId()
if err != nil {
return
}

View File

@ -225,7 +225,7 @@ func (s *sAdminCash) Apply(ctx context.Context, in *adminin.CashApplyInp) (err e
"status": consts.CashStatusWait,
"msg": "",
"ip": location.GetClientIp(ghttp.RequestFromCtx(ctx)),
}).InsertAndGetId()
}).OmitEmptyData().InsertAndGetId()
if err != nil {
return
}

View File

@ -97,7 +97,7 @@ func (s *sAdminCreditsLog) SaveBalance(ctx context.Context, in *adminin.CreditsL
data.Ip = in.Ip
data.MapId = in.MapId
_, err = dao.AdminCreditsLog.Ctx(ctx).Data(data).Insert()
_, err = dao.AdminCreditsLog.Ctx(ctx).Data(data).OmitEmptyData().Insert()
return
}
@ -156,7 +156,7 @@ func (s *sAdminCreditsLog) SaveIntegral(ctx context.Context, in *adminin.Credits
data.Ip = in.Ip
data.MapId = in.MapId
_, err = dao.AdminCreditsLog.Ctx(ctx).Data(data).Insert()
_, err = dao.AdminCreditsLog.Ctx(ctx).Data(data).OmitEmptyData().Insert()
return
}

View File

@ -117,7 +117,7 @@ func (s *sAdminDept) Edit(ctx context.Context, in *adminin.DeptEditInp) (err err
}
// 新增
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Data(in).Insert(); err != nil {
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, "新增部门管理失败,请稍后重试!")
}
return

View File

@ -564,7 +564,7 @@ func (s *sAdminMember) Edit(ctx context.Context, in *adminin.MemberEditInp) (err
}
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
id, err := dao.AdminMember.Ctx(ctx).Data(data).InsertAndGetId()
id, err := dao.AdminMember.Ctx(ctx).Data(data).OmitEmptyData().InsertAndGetId()
if err != nil {
err = gerror.Wrap(err, "新增用户失败,请稍后重试!")
return

View File

@ -31,7 +31,7 @@ func (s *sAdminMemberPost) UpdatePostIds(ctx context.Context, memberId int64, po
}
for i := 0; i < len(postIds); i++ {
_, err = dao.AdminMemberPost.Ctx(ctx).Insert(entity.AdminMemberPost{
_, err = dao.AdminMemberPost.Ctx(ctx).OmitEmptyData().Insert(entity.AdminMemberPost{
MemberId: memberId,
PostId: postIds[i],
})

View File

@ -110,7 +110,7 @@ func (s *sAdminMenu) Edit(ctx context.Context, in *adminin.MenuEditInp) (err err
return err
}
} else {
if _, err = dao.AdminMenu.Ctx(ctx).Data(in).Insert(); err != nil {
if _, err = dao.AdminMenu.Ctx(ctx).Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, "新增菜单失败!")
return err
}

View File

@ -88,7 +88,7 @@ func (s *sAdminNotice) Edit(ctx context.Context, in *adminin.NoticeEditInp) (err
// 新增
in.CreatedBy = member.Id
in.CreatedAt = gtime.Now()
in.Id, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Data(in).InsertAndGetId()
in.Id, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Data(in).OmitEmptyData().InsertAndGetId()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err
@ -426,7 +426,7 @@ func (s *sAdminNotice) updatedReadClicks(ctx context.Context, noticeId, memberId
}
if models == nil {
_, err = dao.AdminNoticeRead.Ctx(ctx).Data(entity.AdminNoticeRead{NoticeId: noticeId, MemberId: memberId}).Insert()
_, err = dao.AdminNoticeRead.Ctx(ctx).Data(entity.AdminNoticeRead{NoticeId: noticeId, MemberId: memberId}).OmitEmptyData().Insert()
return
}
_, err = dao.AdminNoticeRead.Ctx(ctx).Where(dao.AdminNoticeRead.Columns().Id, models.Id).Increment(dao.AdminNoticeRead.Columns().Clicks, 1)

View File

@ -224,7 +224,7 @@ func (s *sAdminOrder) Create(ctx context.Context, in *adminin.OrderCreateInp) (r
Money: in.Money,
Remark: in.Remark,
Status: consts.OrderStatusNotPay,
}).Insert()
}).OmitEmptyData().Insert()
if err != nil {
return
}
@ -348,7 +348,7 @@ func (s *sAdminOrder) Edit(ctx context.Context, in *adminin.OrderEditInp) (err e
FieldsEx(
dao.AdminOrder.Columns().Id,
).
Data(in).Insert()
Data(in).OmitEmptyData().Insert()
return
}

View File

@ -96,7 +96,7 @@ func (s *sAdminPost) Edit(ctx context.Context, in *adminin.PostEditInp) (err err
}
// 新增
_, err = dao.AdminPost.Ctx(ctx).Data(in).Insert()
_, err = dao.AdminPost.Ctx(ctx).Data(in).OmitEmptyData().Insert()
return
}

View File

@ -146,7 +146,7 @@ func (s *sAdminRole) UpdatePermissions(ctx context.Context, in *adminin.UpdatePe
})
}
if _, err = dao.AdminRoleMenu.Ctx(ctx).Data(list).Insert(); err != nil {
if _, err = dao.AdminRoleMenu.Ctx(ctx).Data(list).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return
}
@ -189,7 +189,7 @@ func (s *sAdminRole) Edit(ctx context.Context, in *adminin.RoleEditInp) (err err
}
// 新增
if _, err = dao.AdminRole.Ctx(ctx).Fields(adminin.RoleInsertFields{}).Data(in).Insert(); err != nil {
if _, err = dao.AdminRole.Ctx(ctx).Fields(adminin.RoleInsertFields{}).Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return
}

View File

@ -126,7 +126,7 @@ func (s *sAdminSite) Register(ctx context.Context, in *adminin.RegisterInp) (err
// 提交注册信息
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
id, err := dao.AdminMember.Ctx(ctx).Data(data).InsertAndGetId()
id, err := dao.AdminMember.Ctx(ctx).Data(data).OmitEmptyData().InsertAndGetId()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return

View File

@ -27,15 +27,12 @@ import (
"hotgo/utility/validate"
"net/http"
"strings"
"sync"
)
type sMiddleware struct {
LoginUrl string // 登录路由地址
DemoWhiteList g.Map // 演示模式放行的路由白名单
NotRecordRequest g.Map // 不记录请求数据的路由(当前请求数据过大时会影响响应效率,可以将路径放到该选项中改善)
FilterRoutes map[string]ghttp.RouterItem // 支持预处理的web路由
routeMutex sync.Mutex
}
func init() {

View File

@ -9,58 +9,17 @@ import (
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
"hotgo/internal/global"
"hotgo/internal/library/response"
"hotgo/utility/validate"
"reflect"
)
// GetFilterRoutes 获取支持预处理的web路由
func (s *sMiddleware) GetFilterRoutes(r *ghttp.Request) map[string]ghttp.RouterItem {
// 首次访问时加载
if s.FilterRoutes == nil {
s.routeMutex.Lock()
defer s.routeMutex.Unlock()
if s.FilterRoutes != nil {
return s.FilterRoutes
}
s.FilterRoutes = make(map[string]ghttp.RouterItem)
for _, v := range r.Server.GetRoutes() {
// 非规范路由不加载
if v.Handler.Info.Type.NumIn() != 2 {
continue
}
key := s.GenFilterRouteKey(v.Handler.Router)
if _, ok := s.FilterRoutes[key]; !ok {
s.FilterRoutes[key] = v
}
}
}
return s.FilterRoutes
}
// GenFilterRequestKey 根据请求生成唯一key
func (s *sMiddleware) GenFilterRequestKey(r *ghttp.Request) string {
return s.GenRouteKey(r.Method, r.Request.URL.Path)
}
// GenFilterRouteKey 根据路由生成唯一key
func (s *sMiddleware) GenFilterRouteKey(r *ghttp.Router) string {
return s.GenRouteKey(r.Method, r.Uri)
}
// GenRouteKey 生成唯一key
func (s *sMiddleware) GenRouteKey(method, path string) string {
return method + " " + path
}
// PreFilter 请求输入预处理
// api使用gf规范路由并且XxxReq结构体实现了validate.Filter接口即可
func (s *sMiddleware) PreFilter(r *ghttp.Request) {
router, ok := s.GetFilterRoutes(r)[s.GenFilterRequestKey(r)]
if !ok {
router := global.GetRequestRoute(r)
if router == nil {
r.Middleware.Next()
return
}
@ -89,7 +48,7 @@ func (s *sMiddleware) PreFilter(r *ghttp.Request) {
}
// 没有实现预处理
if _, ok = inputObject.Interface().(validate.Filter); !ok {
if _, ok := inputObject.Interface().(validate.Filter); !ok {
r.Middleware.Next()
return
}

View File

@ -92,7 +92,7 @@ func (s *sPay) Create(ctx context.Context, in payin.PayCreateInp) (res *payin.Pa
}
// 创建支付记录
if _, err = s.Model(ctx).Data(data).Insert(); err != nil {
if _, err = s.Model(ctx).Data(data).OmitEmptyData().Insert(); err != nil {
return
}

View File

@ -110,7 +110,7 @@ func (s *sPay) Edit(ctx context.Context, in payin.PayEditInp) (err error) {
}
// 新增
_, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Data(in).Insert()
_, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Data(in).OmitEmptyData().Insert()
return
}

View File

@ -132,7 +132,7 @@ func (s *sPayRefund) Refund(ctx context.Context, in *payin.PayRefundInp) (res *p
}
// 创建退款记录
if _, err = s.Model(ctx).Data(data).Insert(); err != nil {
if _, err = s.Model(ctx).Data(data).OmitEmptyData().Insert(); err != nil {
return
}
return

View File

@ -60,7 +60,7 @@ func (s *sSysBlacklist) Edit(ctx context.Context, in *sysin.BlacklistEditInp) (e
}
// 新增
_, err = dao.SysBlacklist.Ctx(ctx).Data(in).Insert()
_, err = dao.SysBlacklist.Ctx(ctx).Data(in).OmitEmptyData().Insert()
return
}

View File

@ -89,7 +89,7 @@ func (s *sSysCron) Edit(ctx context.Context, in *sysin.CronEditInp) (err error)
}
// 新增
in.SysCron.Id, err = dao.SysCron.Ctx(ctx).Data(in).InsertAndGetId()
in.SysCron.Id, err = dao.SysCron.Ctx(ctx).Data(in).OmitEmptyData().InsertAndGetId()
if err != nil || in.SysCron.Id < 1 {
return
}

View File

@ -43,7 +43,7 @@ func (s *sSysCronGroup) Edit(ctx context.Context, in *sysin.CronGroupEditInp) (e
}
// 新增
if _, err = dao.SysCronGroup.Ctx(ctx).Fields(sysin.CronGroupInsertFields{}).Data(in).Insert(); err != nil {
if _, err = dao.SysCronGroup.Ctx(ctx).Fields(sysin.CronGroupInsertFields{}).Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
}
return

View File

@ -161,7 +161,7 @@ func (s *sSysCurdDemo) Edit(ctx context.Context, in *sysin.CurdDemoEditInp) (err
in.CreatedBy = contexts.GetUserId(ctx)
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
Fields(sysin.CurdDemoInsertFields{}).
Data(in).Insert(); err != nil {
Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, "新增CURD列表失败请稍后重试")
}
return

View File

@ -60,7 +60,7 @@ func (s *sSysDictData) Edit(ctx context.Context, in *sysin.DictDataEditInp) (err
return gerror.Wrap(err, "类型选择无效,请检查")
}
_, err = dao.SysDictData.Ctx(ctx).Fields(sysin.DictDataInsertFields{}).Data(in).Insert()
_, err = dao.SysDictData.Ctx(ctx).Fields(sysin.DictDataInsertFields{}).Data(in).OmitEmptyData().Insert()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return err

View File

@ -96,7 +96,7 @@ func (s *sSysDictType) Edit(ctx context.Context, in *sysin.DictTypeEditInp) (err
}
// 新增
if _, err = dao.SysDictType.Ctx(ctx).Fields(sysin.DictTypeInsertFields{}).Data(in).Insert(); err != nil {
if _, err = dao.SysDictType.Ctx(ctx).Fields(sysin.DictTypeInsertFields{}).Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
}
return

View File

@ -61,7 +61,7 @@ func (s *sSysEmsLog) Edit(ctx context.Context, in *sysin.EmsLogEditInp) (err err
}
// 新增
_, err = dao.SysEmsLog.Ctx(ctx).Data(in).Insert()
_, err = dao.SysEmsLog.Ctx(ctx).Data(in).OmitEmptyData().Insert()
return
}
@ -190,7 +190,7 @@ func (s *sSysEmsLog) Send(ctx context.Context, in *sysin.SendEmsInp) (err error)
data.CreatedAt = gtime.Now()
data.UpdatedAt = gtime.Now()
_, err = dao.SysEmsLog.Ctx(ctx).Data(data).Insert()
_, err = dao.SysEmsLog.Ctx(ctx).Data(data).OmitEmptyData().Insert()
return
}

View File

@ -88,7 +88,7 @@ func (s *sSysGenCodes) Edit(ctx context.Context, in *sysin.GenCodesEditInp) (res
in.MasterColumns = gjson.New("[]")
in.Status = consts.GenCodesStatusWait
in.CreatedAt = gtime.Now()
id, err := dao.SysGenCodes.Ctx(ctx).Data(in).InsertAndGetId()
id, err := dao.SysGenCodes.Ctx(ctx).Data(in).OmitEmptyData().InsertAndGetId()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return

View File

@ -7,6 +7,8 @@ package sys
import (
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
@ -17,7 +19,9 @@ import (
"github.com/gogf/gf/v2/util/gconv"
"hotgo/internal/consts"
"hotgo/internal/dao"
"hotgo/internal/global"
"hotgo/internal/library/contexts"
"hotgo/internal/library/dict"
"hotgo/internal/library/hgorm/handler"
"hotgo/internal/library/hgorm/hook"
"hotgo/internal/library/location"
@ -40,6 +44,11 @@ func init() {
service.RegisterSysLog(NewSysLog())
}
// Model 请求日志Orm模型
func (s *sSysLog) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
return handler.Model(dao.SysLog.Ctx(ctx), option...)
}
// Export 导出
func (s *sSysLog) Export(ctx context.Context, in *sysin.LogListInp) (err error) {
// 导出格式
@ -96,7 +105,7 @@ func (s *sSysLog) Export(ctx context.Context, in *sysin.LogListInp) (err error)
// RealWrite 真实写入
func (s *sSysLog) RealWrite(ctx context.Context, log entity.SysLog) (err error) {
_, err = dao.SysLog.Ctx(ctx).FieldsEx(dao.SysLog.Columns().Id).Data(log).Insert()
_, err = dao.SysLog.Ctx(ctx).FieldsEx(dao.SysLog.Columns().Id).Data(log).Unscoped().OmitEmptyData().Insert()
return
}
@ -111,7 +120,11 @@ func (s *sSysLog) AutoLog(ctx context.Context) error {
}()
config, err := service.SysConfig().GetLoadLog(ctx)
if err != nil || !config.Switch {
if err != nil {
return
}
if config == nil || !config.Switch {
return
}
@ -167,6 +180,10 @@ func (s *sSysLog) AnalysisLog(ctx context.Context) entity.SysLog {
}
}
if timestamp == 0 {
timestamp = gtime.Timestamp()
}
// 请求头
if reqHeadersBytes, _ := gjson.New(request.Header).MarshalJSON(); len(reqHeadersBytes) > 0 {
headerData = gjson.New(reqHeadersBytes)
@ -209,6 +226,8 @@ func (s *sSysLog) AnalysisLog(ctx context.Context) entity.SysLog {
takeUpTime = tt
}
headerData.MustSet("qqq", request.EnterTime.String())
data = entity.SysLog{
AppId: appId,
MerchantId: 0,
@ -230,13 +249,28 @@ func (s *sSysLog) AnalysisLog(ctx context.Context) entity.SysLog {
UserAgent: request.Header.Get("User-Agent"),
Status: consts.StatusEnabled,
TakeUpTime: takeUpTime,
UpdatedAt: gtime.Now(),
CreatedAt: request.EnterTime,
}
return data
}
// View 获取指定字典类型信息
// View 获取指定请求日志信息
func (s *sSysLog) View(ctx context.Context, in *sysin.LogViewInp) (res *sysin.LogViewModel, err error) {
if err = dao.SysLog.Ctx(ctx).Handler(handler.FilterAuth).Hook(hook.CityLabel).Where("id", in.Id).Scan(&res); err != nil {
mod := s.Model(ctx)
count, err := service.SysLoginLog().Model(ctx).
LeftJoinOnFields(dao.SysLog.Table(), dao.SysLoginLog.Columns().ReqId, "=", dao.SysLog.Columns().ReqId).
WherePrefix(dao.SysLog.Table(), dao.SysLog.Columns().Id, in.Id).Count()
if err != nil {
return nil, err
}
if count > 0 {
mod = dao.SysLog.Ctx(ctx)
}
if err = mod.Hook(hook.CityLabel).WherePri(in.Id).Scan(&res); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return
}
@ -245,6 +279,15 @@ func (s *sSysLog) View(ctx context.Context, in *sysin.LogViewInp) (res *sysin.Lo
return
}
routes := global.LoadHTTPRoutes(ghttp.RequestFromCtx(ctx))
key := global.GenRouteKey(res.Method, res.Url)
route, ok := routes[key]
if ok {
res.Tags = route.Tags
res.Summary = route.Summary
res.Description = route.Description
}
if simple.IsDemo(ctx) {
res.HeaderData = gjson.New(`{
"none": [
@ -255,15 +298,15 @@ func (s *sSysLog) View(ctx context.Context, in *sysin.LogViewInp) (res *sysin.Lo
return
}
// Delete 删除
// Delete 删除请求日志
func (s *sSysLog) Delete(ctx context.Context, in *sysin.LogDeleteInp) (err error) {
_, err = dao.SysLog.Ctx(ctx).Handler(handler.FilterAuth).Where("id", in.Id).Delete()
_, err = s.Model(ctx).WherePri(in.Id).Delete()
return
}
// List 列表
// List 请求日志列表
func (s *sSysLog) List(ctx context.Context, in *sysin.LogListInp) (list []*sysin.LogListModel, totalCount int, err error) {
mod := dao.SysLog.Ctx(ctx).Handler(handler.FilterAuth).FieldsEx("get_data", "header_data", "post_data")
mod := s.Model(ctx).FieldsEx("get_data", "header_data", "post_data")
// 访问路径
if in.Url != "" {
@ -275,6 +318,11 @@ func (s *sSysLog) List(ctx context.Context, in *sysin.LogListInp) (list []*sysin
mod = mod.Where("module", in.Module)
}
// 链路ID
if in.ReqId != "" {
mod = mod.Where("req_id", in.ReqId)
}
// 请求方式
if in.Method != "" {
mod = mod.Where("method", in.Method)
@ -301,8 +349,8 @@ func (s *sSysLog) List(ctx context.Context, in *sysin.LogListInp) (list []*sysin
}
// 请求耗时
if in.TakeUpTime > 0 {
mod = mod.WhereGTE("take_up_time", in.TakeUpTime)
if dict.HasOptionKey(consts.HTTPHandlerTimeOptions, in.TakeUpTime) {
mod = mod.Where(fmt.Sprintf("`take_up_time` %v", in.TakeUpTime))
}
totalCount, err = mod.Count()
@ -310,35 +358,40 @@ func (s *sSysLog) List(ctx context.Context, in *sysin.LogListInp) (list []*sysin
return
}
if err = mod.Page(in.Page, in.PerPage).Order("id desc").Scan(&list); err != nil {
if err = mod.Page(in.Page, in.PerPage).Hook(hook.CityLabel).Order("id desc").Scan(&list); err != nil {
return
}
for i := 0; i < len(list); i++ {
// 管理员
if list[i].AppId == consts.AppAdmin {
memberName, err := dao.AdminMember.Ctx(ctx).Fields("realname").Where("id", list[i].MemberId).Value()
routes := global.LoadHTTPRoutes(ghttp.RequestFromCtx(ctx))
for _, v := range list {
if v.AppId == consts.AppAdmin {
memberName, err := dao.AdminMember.Ctx(ctx).Fields("realname").WherePri(v.MemberId).Value()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return list, totalCount, err
}
list[i].MemberName = memberName.String()
v.MemberName = memberName.String()
}
// 接口
// ...
if list[i].MemberName == "" {
list[i].MemberName = "游客"
if v.MemberName == "" {
v.MemberName = "游客"
}
// 截取请求url路径
if gstr.Contains(list[i].Url, "?") {
list[i].Url = gstr.StrTillEx(list[i].Url, "?")
if gstr.Contains(v.Url, "?") {
v.Url = gstr.StrTillEx(v.Url, "?")
}
key := global.GenRouteKey(v.Method, v.Url)
route, ok := routes[key]
if ok {
v.Tags = route.Tags
v.Summary = route.Summary
v.Description = route.Description
}
if simple.IsDemo(ctx) {
list[i].HeaderData = gjson.New(`{
v.HeaderData = gjson.New(`{
"none": [
"` + consts.DemoTips + `"
]

View File

@ -43,13 +43,13 @@ func init() {
}
// Model 登录日志Orm模型
func (s *sSysLoginLog) Model(ctx context.Context) *gdb.Model {
return dao.SysLoginLog.Ctx(ctx)
func (s *sSysLoginLog) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
return handler.Model(dao.SysLoginLog.Ctx(ctx), option...)
}
// List 获取登录日志列表
func (s *sSysLoginLog) List(ctx context.Context, in *sysin.LoginLogListInp) (list []*sysin.LoginLogListModel, totalCount int, err error) {
mod := dao.SysLoginLog.Ctx(ctx)
mod := s.Model(ctx)
// 查询状态
if in.Status > 0 {
@ -76,7 +76,7 @@ func (s *sSysLoginLog) List(ctx context.Context, in *sysin.LoginLogListInp) (lis
return
}
if err = mod.Fields(sysin.LoginLogListModel{}).Hook(hook.CityLabel).Handler(handler.FilterAuth).Page(in.Page, in.PerPage).OrderDesc(dao.SysLoginLog.Columns().Id).Scan(&list); err != nil {
if err = mod.Fields(sysin.LoginLogListModel{}).Hook(hook.CityLabel).Page(in.Page, in.PerPage).OrderDesc(dao.SysLoginLog.Columns().Id).Scan(&list); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return list, totalCount, err
}
@ -121,13 +121,7 @@ func (s *sSysLoginLog) Export(ctx context.Context, in *sysin.LoginLogListInp) (e
// Delete 删除登录日志
func (s *sSysLoginLog) Delete(ctx context.Context, in *sysin.LoginLogDeleteInp) (err error) {
_, err = dao.SysLoginLog.Ctx(ctx).Where(dao.SysLoginLog.Columns().Id, in.Id).Delete()
return
}
// View 获取登录日志指定信息
func (s *sSysLoginLog) View(ctx context.Context, in *sysin.LoginLogViewInp) (res *sysin.LoginLogViewModel, err error) {
err = dao.SysLoginLog.Ctx(ctx).Where(dao.SysLoginLog.Columns().Id, in.Id).Scan(&res)
_, err = s.Model(ctx).WherePri(in.Id).Delete()
return
}
@ -181,6 +175,6 @@ func (s *sSysLoginLog) Push(ctx context.Context, in *sysin.LoginLogPushInp) {
// RealWrite 真实写入
func (s *sSysLoginLog) RealWrite(ctx context.Context, models entity.SysLoginLog) (err error) {
_, err = dao.SysLoginLog.Ctx(ctx).FieldsEx(dao.SysLog.Columns().Id).Data(models).Insert()
_, err = dao.SysLoginLog.Ctx(ctx).Data(models).OmitEmptyData().Insert()
return
}

View File

@ -113,7 +113,7 @@ func (s *sSysNormalTreeDemo) Edit(ctx context.Context, in *sysin.NormalTreeDemoE
in.CreatedBy = contexts.GetUserId(ctx)
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
Fields(sysin.NormalTreeDemoInsertFields{}).
Data(in).Insert(); err != nil {
Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, "新增普通树表失败,请稍后重试!")
}
return

View File

@ -113,7 +113,7 @@ func (s *sSysOptionTreeDemo) Edit(ctx context.Context, in *sysin.OptionTreeDemoE
in.CreatedBy = contexts.GetUserId(ctx)
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
Fields(sysin.OptionTreeDemoInsertFields{}).
Data(in).Insert(); err != nil {
Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, "新增选项树表失败,请稍后重试!")
}
return

View File

@ -92,7 +92,7 @@ func (s *sSysProvinces) Edit(ctx context.Context, in *sysin.ProvincesEditInp) (e
}
// 新增
if _, err = dao.SysProvinces.Ctx(ctx).Fields(sysin.ProvincesInsertFields{}).Data(in).Insert(); err != nil {
if _, err = dao.SysProvinces.Ctx(ctx).Fields(sysin.ProvincesInsertFields{}).Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, "新增省市区数据失败!")
}
return

View File

@ -142,7 +142,7 @@ func (s *sSysServeLicense) Edit(ctx context.Context, in *sysin.ServeLicenseEditI
}
// 新增
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Fields(sysin.ServeLicenseInsertFields{}).Data(in).Insert(); err != nil {
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Fields(sysin.ServeLicenseInsertFields{}).Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, "新增服务许可证失败,请稍后重试!")
}
return

View File

@ -126,6 +126,6 @@ func (s *sSysServeLog) View(ctx context.Context, in *sysin.ServeLogViewInp) (res
// RealWrite 真实写入
func (s *sSysServeLog) RealWrite(ctx context.Context, models entity.SysServeLog) (err error) {
_, err = dao.SysServeLog.Ctx(ctx).FieldsEx(dao.SysLog.Columns().Id).Data(models).Insert()
_, err = dao.SysServeLog.Ctx(ctx).FieldsEx(dao.SysLog.Columns().Id).Data(models).OmitEmptyData().Insert()
return
}

View File

@ -138,7 +138,7 @@ func (s *sSysSmsLog) SendCode(ctx context.Context, in *sysin.SendCodeInp) (err e
data.CreatedAt = gtime.Now()
data.UpdatedAt = gtime.Now()
_, err = dao.SysSmsLog.Ctx(ctx).Data(data).Insert()
_, err = dao.SysSmsLog.Ctx(ctx).Data(data).OmitEmptyData().Insert()
return
}

View File

@ -98,7 +98,7 @@ func (s *sSysTestCategory) Edit(ctx context.Context, in *sysin.TestCategoryEditI
// 新增
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
Fields(sysin.TestCategoryInsertFields{}).
Data(in).Insert(); err != nil {
Data(in).OmitEmptyData().Insert(); err != nil {
err = gerror.Wrap(err, "新增测试分类失败,请稍后重试!")
}
return

View File

@ -147,7 +147,7 @@ type MemberAddInp struct {
}
func (in *MemberEditInp) Filter(ctx context.Context) (err error) {
if in.Password != "" {
if in.Id < 1 || in.Password != "" {
if err := g.Validator().
Rules("length:6,16").
Messages("新密码不能为空#新密码需在6~16之间").

View File

@ -14,20 +14,25 @@ import (
type LogListInp struct {
form.PageReq
form.StatusReq
ReqId string `json:"reqId" dc:"对外ID"`
Module string `json:"module" dc:"应用端口"`
MemberId int `json:"member_id" dc:"用户ID"`
TakeUpTime int `json:"take_up_time" dc:"请求耗时"`
MemberId int `json:"memberId" dc:"用户ID"`
TakeUpTime string `json:"takeUpTime" dc:"请求耗时"`
Method string `json:"method" dc:"请求方式"`
Url string `json:"url" dc:"请求路径"`
Ip string `json:"ip" dc:"访问IP"`
ErrorCode string `json:"error_code" dc:"状态码"`
CreatedAt []int64 `json:"created_at" dc:"创建时间"`
ErrorCode string `json:"errorCode" dc:"状态码"`
CreatedAt []int64 `json:"createdAt" dc:"创建时间"`
}
type LogListModel struct {
entity.SysLog
MemberName string `json:"memberName"`
Region string `json:"region"`
CityLabel string `json:"cityLabel" dc:"城市标签"`
Tags string `json:"tags" dc:"接口所属的标签,用于接口分类"`
Summary string `json:"summary" dc:"接口/参数概要描述"`
Description string `json:"description" dc:"接口/参数详细描述"`
}
// LogViewInp 获取信息
@ -37,7 +42,10 @@ type LogViewInp struct {
type LogViewModel struct {
entity.SysLog
CityLabel string `json:"cityLabel" description:"城市标签"`
CityLabel string `json:"cityLabel" dc:"城市标签"`
Tags string `json:"tags" dc:"接口所属的标签,用于接口分类"`
Summary string `json:"summary" dc:"接口/参数概要描述"`
Description string `json:"description" dc:"接口/参数详细描述"`
}
// LogDeleteInp 删除

File diff suppressed because one or more lines are too long

View File

@ -37,14 +37,6 @@ type (
Blacklist(r *ghttp.Request)
// Develop 开发工具白名单过滤
Develop(r *ghttp.Request)
// GetFilterRoutes 获取支持预处理的web路由
GetFilterRoutes(r *ghttp.Request) map[string]ghttp.RouterItem
// GenFilterRequestKey 根据请求生成唯一key
GenFilterRequestKey(r *ghttp.Request) string
// GenFilterRouteKey 根据路由生成唯一key
GenFilterRouteKey(r *ghttp.Router) string
// GenRouteKey 生成唯一key
GenRouteKey(method, path string) string
// PreFilter 请求输入预处理
// api使用gf规范路由并且XxxReq结构体实现了validate.Filter接口即可
PreFilter(r *ghttp.Request)

View File

@ -243,6 +243,8 @@ type (
Build(ctx context.Context, in *sysin.GenCodesBuildInp) (err error)
}
ISysLog interface {
// Model 请求日志Orm模型
Model(ctx context.Context, option ...*handler.Option) *gdb.Model
// Export 导出
Export(ctx context.Context, in *sysin.LogListInp) (err error)
// RealWrite 真实写入
@ -251,24 +253,22 @@ type (
AutoLog(ctx context.Context) error
// AnalysisLog 解析日志数据
AnalysisLog(ctx context.Context) entity.SysLog
// View 获取指定字典类型信息
// View 获取指定请求日志信息
View(ctx context.Context, in *sysin.LogViewInp) (res *sysin.LogViewModel, err error)
// Delete 删除
// Delete 删除请求日志
Delete(ctx context.Context, in *sysin.LogDeleteInp) (err error)
// List 列表
// List 请求日志列表
List(ctx context.Context, in *sysin.LogListInp) (list []*sysin.LogListModel, totalCount int, err error)
}
ISysLoginLog interface {
// Model 登录日志Orm模型
Model(ctx context.Context) *gdb.Model
Model(ctx context.Context, option ...*handler.Option) *gdb.Model
// List 获取登录日志列表
List(ctx context.Context, in *sysin.LoginLogListInp) (list []*sysin.LoginLogListModel, totalCount int, err error)
// Export 导出登录日志
Export(ctx context.Context, in *sysin.LoginLogListInp) (err error)
// Delete 删除登录日志
Delete(ctx context.Context, in *sysin.LoginLogDeleteInp) (err error)
// View 获取登录日志指定信息
View(ctx context.Context, in *sysin.LoginLogViewInp) (res *sysin.LoginLogViewModel, err error)
// Push 推送登录日志
Push(ctx context.Context, in *sysin.LoginLogPushInp)
// RealWrite 真实写入

View File

@ -45,7 +45,7 @@ module.exports = defineConfig({
{ varsIgnorePattern: '.*', args: 'none' },
],
'space-before-function-paren': 'off',
'vue/multi-word-component-names': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',

View File

@ -68,38 +68,38 @@
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-vue": "^3.2.0",
"@vitejs/plugin-vue-jsx": "^2.1.1",
"@vue/compiler-sfc": "^3.2.33",
"@vue/eslint-config-typescript": "^7.0.0",
"autoprefixer": "^10.4.7",
"commitizen": "^4.2.4",
"core-js": "^3.22.5",
"crypto-js": "^4.1.1",
"dotenv": "^10.0.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.5.0",
"eslint-define-config": "1.0.9",
"eslint-plugin-jest": "^24.7.0",
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-vue": "^7.20.0",
"esno": "^0.7.3",
"gh-pages": "^3.2.3",
"husky": "^6.0.0",
"jest": "^27.5.1",
"less": "^4.1.2",
"less-loader": "^9.1.0",
"lint-staged": "^11.2.6",
"@vue/compiler-sfc": "^3.4.21",
"@vue/eslint-config-typescript": "^11.0.3",
"autoprefixer": "^10.4.19",
"commitizen": "^4.3.0",
"core-js": "^3.36.1",
"crypto-js": "^4.2.0",
"dotenv": "^16.4.5",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.10.0",
"eslint-define-config": "1.12.0",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.24.1",
"esno": "^0.16.3",
"gh-pages": "^4.0.0",
"husky": "^8.0.3",
"jest": "^29.7.0",
"less": "^4.2.0",
"less-loader": "^11.1.4",
"lint-staged": "^13.3.0",
"postcss": "^8.4.38",
"prettier": "^2.6.2",
"pretty-quick": "^3.1.3",
"prettier": "^2.8.8",
"pretty-quick": "^3.3.1",
"rimraf": "^3.0.2",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^22.0.0",
"stylelint-order": "^4.1.0",
"stylelint-scss": "^3.21.0",
"stylelint": "^14.16.1",
"stylelint-config-prettier": "^9.0.5",
"stylelint-config-standard": "^29.0.0",
"stylelint-order": "^5.0.0",
"stylelint-scss": "^4.7.0",
"tailwindcss": "^2.2.19",
"typescript": "^5.3.0",
"unplugin-vue-components": "^0.17.21",
"unplugin-vue-components": "^0.22.12",
"vite": "^4.5.3",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^2.1.2",

View File

@ -18,15 +18,6 @@ export function Delete(params) {
});
}
// 获取登录日志指定详情
export function View(params) {
return http.request({
url: '/loginLog/view',
method: 'GET',
params,
});
}
// 导出登录日志
export function Export(params) {
jumpExport('/loginLog/export', params);

View File

@ -1,13 +1,15 @@
<template>
<RouterView>
<template #default="{ Component, route }">
{{ retryKeepAlive(route) }}
{{ setKeepAlive(route) }}
<template v-if="mode === 'production'">
<transition :name="getTransitionName" appear mode="out-in">
<div>
<keep-alive v-if="keepAliveComponents.length" :include="keepAliveComponents">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
<component :is="Component" v-else :key="route.fullPath" />
</div>
</transition>
</template>
<template v-else>
@ -40,12 +42,11 @@
},
},
setup() {
const mode = import.meta.env.MODE;
const router = useRouter();
const { getIsPageAnimate, getPageAnimateType } = useProjectSetting();
const asyncRouteStore = useAsyncRouteStore();
//
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents);
const getTransitionName = computed(() => {
return unref(getIsPageAnimate) ? unref(getPageAnimateType) : '';
});
@ -56,7 +57,7 @@
return currentComponent.name || currentComponent.__name;
}
function retryKeepAlive(route) {
function setKeepAlive(route) {
if (!route?.meta?.keepAlive) {
return;
}
@ -65,26 +66,15 @@
if (currentName === undefined || route.name === undefined) {
return;
}
const index = keepAliveComponents.value.findIndex((name) => name === route.name);
if (index > -1 && currentName !== route.name) {
const index2 = keepAliveComponents.value.findIndex((name) => name === currentName);
if (index2 === -1) {
console.warn(
`the routing name configured on the backend is inconsistent with the component name in the. vue file. KeepAlive has been retried based on the actual component name, but this may cause unexpected issues.\n routeName:` +
route.name +
',currentName:' +
currentName
);
if (!keepAliveComponents.value.includes(currentName)) {
asyncRouteStore.keepAliveComponents.push(currentName);
}
}
}
const mode = import.meta.env.MODE;
return {
keepAliveComponents,
getTransitionName,
retryKeepAlive,
setKeepAlive,
mode,
};
},

View File

@ -102,21 +102,6 @@ export function createRouterGuards(router: Router) {
if (isNavigationFailure(failure)) {
//console.log('failed navigation', failure)
}
const asyncRouteStore = useAsyncRouteStoreWidthOut();
// 在这里设置需要缓存的组件名称
const keepAliveComponents = asyncRouteStore.keepAliveComponents;
const currentComName: any = to.matched.find((item) => item.name == to.name)?.name;
if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) {
// 需要缓存的组件
keepAliveComponents.push(currentComName);
} else if (!to.meta?.keepAlive || to.name == 'Redirect') {
// 不需要缓存的组件
const index = asyncRouteStore.keepAliveComponents.findIndex((name) => name == currentComName);
if (index != -1) {
keepAliveComponents.splice(index, 1);
}
}
asyncRouteStore.setKeepAliveComponents(keepAliveComponents);
const Loading = window['$loading'] || null;
Loading && Loading.finish();
});

View File

@ -49,21 +49,29 @@ export default () => {
let timer: ReturnType<typeof setTimeout>;
const createSocket = () => {
console.log('[WebSocket] createSocket...');
if (useUserStore.token === '') {
if (useUserStore.token === '' || useUserStore.config?.wsAddr == '') {
console.error('[WebSocket] 用户未登录,稍后重试...');
reconnect();
resetReconnect();
return;
}
try {
socket = new WebSocket(`${useUserStore.config?.wsAddr}?authorization=${useUserStore.token}`);
init();
} catch (e) {
console.error(`[WebSocket] createSocket err: ${e}`);
reconnect();
}
if (lockReconnect) {
lockReconnect = false;
}
} catch (e) {
console.error(`[WebSocket] createSocket err: ${e}`);
resetReconnect();
return;
}
};
const resetReconnect = () => {
if (lockReconnect) {
lockReconnect = false;
}
reconnect();
};
const reconnect = () => {
@ -73,7 +81,7 @@ export default () => {
clearTimeout(timer);
timer = setTimeout(() => {
createSocket();
}, SocketEnum.HeartBeatInterval);
}, 2000);
};
const init = () => {

View File

@ -64,9 +64,9 @@
{
field: 'url',
component: 'NInput',
label: '访问路径',
label: '接口路径',
componentProps: {
placeholder: '请输入访问路径',
placeholder: '请输入接口路径',
onInput: (e: any) => {
console.log(e);
},

View File

@ -1,84 +1,147 @@
import { h } from 'vue';
import { NTag } from 'naive-ui';
import { NTag, NEllipsis, NSpace } from 'naive-ui';
import { timestampToTime } from '@/utils/dateUtil';
import { renderHtmlTooltip } from '@/utils';
export const columns = [
{
title: '模块',
key: 'module',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: row.module == 'admin' ? 'info' : 'success',
bordered: false,
},
{
default: () => row.module,
}
);
},
title: '记录ID',
key: 'id',
width: 100,
},
{
title: '操作人',
key: 'memberName',
render(row) {
if (row.memberId === 0) {
return row.memberName;
}
return row.memberName + '(' + row.memberId + ')';
},
width: 150,
},
{
title: '请求方式',
key: 'method',
width: 80,
},
{
title: '请求路径',
key: 'url',
width: 200,
},
{
title: '访问IP',
key: 'ip',
width: 150,
},
// {
// title: 'IP地区',
// key: 'region',
// },
{
title: '状态码',
key: 'errorCode',
title: '访客',
key: 'name',
width: 180,
render(row) {
const operator =
row.memberId === 0 ? row.memberName : row.memberName + '(' + row.memberId + ')';
return h(
NEllipsis,
{
style: {
maxWidth: '180px',
},
},
{
default: () =>
h(
NSpace,
{ vertical: true },
{
default: () => [
h('div', {
innerHTML: '<div><p>' + operator + '</p></div>',
}),
h('div', {
innerHTML: '<div><p>IP' + row.ip + '</p></div>',
}),
row.cityLabel != ''
? h(
NTag,
{
style: {
marginRight: '6px',
},
type: row.errorCode == 0 ? 'success' : 'warning',
type: 'primary',
bordered: false,
},
{
default: () => row.errorMsg + '(' + row.errorCode + ')',
default: () => row.cityLabel,
}
)
: null,
],
}
),
}
);
},
width: 150,
},
{
title: '处理耗时',
key: 'takeUpTime',
title: '请求接口',
key: 'name',
width: 260,
render(row) {
return row.takeUpTime + ' ms';
return h(
NEllipsis,
{
style: {
maxWidth: '260px',
},
},
{
default: () =>
h(
NSpace,
{ vertical: true },
{
default: () => [
h(
NTag,
{
style: {
marginRight: '6px',
},
bordered: false,
},
{
default: () => row.method,
}
),
h('div', {
innerHTML: '<div><p>接口:' + row.url + '</p></div>',
}),
h('div', {
innerHTML: '<div><p>名称:' + row.tags + ' / ' + row.summary + '</p></div>',
}),
],
}
),
}
);
},
},
{
title: '接口响应',
key: 'name',
width: 260,
render(row) {
return h(
NEllipsis,
{
style: {
maxWidth: '260px',
},
},
{
default: () =>
h(
NSpace,
{ vertical: true },
{
default: () => [
renderHtmlTooltip(
'<div style="width: 240px"><p>状态码:' +
row.errorMsg +
'(' +
row.errorCode +
')' +
'</p></div>'
),
h('div', {
innerHTML: '<div><p>处理耗时:' + row.takeUpTime + 'ms</p></div>',
}),
h('div', {
innerHTML: '<div><p>响应时间:' + timestampToTime(row.timestamp) + '</p></div>',
}),
],
}
),
}
);
},
width: 120,
},
{
title: '访问时间',

View File

@ -39,150 +39,26 @@
</template>
<script lang="ts" setup>
import { computed, h, reactive, ref } from 'vue';
import { computed, h, onMounted, reactive, ref } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
import { BasicForm, useForm } from '@/components/Form/index';
import { getLogList, Delete } from '@/api/log/log';
import { columns } from './columns';
import { useRouter } from 'vue-router';
import { DeleteOutlined } from '@vicons/antd';
import { adaTableScrollX } from '@/utils/hotgo';
import { loadOptions, schemas } from './model';
const dialog = useDialog();
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const schemas: FormSchema[] = [
{
field: 'member_id',
component: 'NInput',
label: '操作人',
componentProps: {
placeholder: '请输入操作人ID',
onInput: (e: any) => {
console.log(e);
},
},
rules: [{ trigger: ['blur'] }],
},
{
field: 'url',
component: 'NInput',
label: '访问路径',
componentProps: {
placeholder: '请输入访问路径',
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'ip',
component: 'NInput',
label: '访问IP',
componentProps: {
placeholder: '请输入IP地址',
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'method',
component: 'NSelect',
label: '请求方式',
componentProps: {
placeholder: '请选择请求方式',
options: [
{
label: 'GET',
value: 'GET',
},
{
label: 'POST',
value: 'POST',
},
],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'created_at',
component: 'NDatePicker',
label: '访问时间',
componentProps: {
type: 'datetimerange',
clearable: true,
// defaultValue: [new Date() - 86400000 * 30, new Date()],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'take_up_time',
component: 'NSelect',
label: '请求耗时',
componentProps: {
placeholder: '请选择请求耗时',
options: [
{
label: '50ms内',
value: '50',
},
{
label: '100ms内',
value: '100',
},
{
label: '200ms内',
value: '200',
},
{
label: '500ms内',
value: '500',
},
],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'error_code',
component: 'NSelect',
label: '状态码',
componentProps: {
placeholder: '请选择状态码',
options: [
{
label: '0 成功',
value: '0',
},
{
label: '-1 失败',
value: '-1',
},
],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
];
const router = useRouter();
const message = useMessage();
const actionRef = ref();
const formParams = ref({});
const params = ref({
pageSize: 10,
});
const actionColumn = reactive({
width: 160,
title: '操作',
@ -233,9 +109,6 @@
reloadTable();
});
},
onNegativeClick: () => {
// message.error('');
},
});
}
@ -251,14 +124,11 @@
reloadTable();
});
},
onNegativeClick: () => {
// message.error('');
},
});
}
const loadDataTable = async (res) => {
return await getLogList({ ...formParams.value, ...params.value, ...res });
return await getLogList({ ...formParams.value, ...res });
};
function reloadTable() {
@ -278,6 +148,10 @@
formParams.value = {};
reloadTable();
}
onMounted(() => {
loadOptions();
});
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,134 @@
import { FormSchema } from '@/components/Form';
import { ref } from 'vue';
import { defRangeShortcuts } from '@/utils/dateUtil';
import { Option } from '@/utils/hotgo';
import { Dicts } from '@/api/dict/dict';
export const schemas = ref<FormSchema[]>([
{
field: 'reqId',
component: 'NInput',
label: '链路ID',
componentProps: {
placeholder: '请输入链路ID',
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'memberId',
component: 'NInput',
label: '操作人',
componentProps: {
placeholder: '请输入操作人ID',
onInput: (e: any) => {
console.log(e);
},
},
rules: [{ trigger: ['blur'] }],
},
{
field: 'url',
component: 'NInput',
label: '接口路径',
componentProps: {
placeholder: '请输入接口路径',
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'ip',
component: 'NInput',
label: '访问IP',
componentProps: {
placeholder: '请输入IP地址',
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'method',
component: 'NSelect',
label: '请求方式',
componentProps: {
placeholder: '请选择请求方式',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'createdAt',
component: 'NDatePicker',
label: '访问时间',
componentProps: {
type: 'datetimerange',
clearable: true,
shortcuts: defRangeShortcuts(),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'takeUpTime',
component: 'NSelect',
label: '请求耗时',
componentProps: {
placeholder: '请选择请求耗时',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'errorCode',
component: 'NSelect',
label: '状态码',
labelMessage: '支持填入自定义状态码',
componentProps: {
placeholder: '请选择状态码',
options: [],
filterable: true,
tag: true,
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
// 字典数据选项
export const options = ref({
HTTPMethod: [] as Option[],
HTTPHandlerTime: [] as Option[],
HTTPApiCode: [] as Option[],
});
// 加载字典数据选项
export function loadOptions() {
Dicts({
types: ['HTTPMethod', 'HTTPHandlerTime', 'HTTPApiCode'],
}).then((res) => {
options.value = res;
for (const item of schemas.value) {
switch (item.field) {
case 'method':
item.componentProps.options = options.value.HTTPMethod;
break;
case 'takeUpTime':
item.componentProps.options = options.value.HTTPHandlerTime;
break;
case 'errorCode':
item.componentProps.options = options.value.HTTPApiCode;
break;
}
}
});
}

View File

@ -14,15 +14,18 @@
<template #label>请求地址</template>
{{ data.url }}
</n-descriptions-item>
<n-descriptions-item label="请求耗时">{{ data.takeUpTime }} ms</n-descriptions-item>
<n-descriptions-item label="接口名称"
>{{ data.tags }} / {{ data.summary }}</n-descriptions-item
>
<n-descriptions-item label="访问IP">{{ data.ip }}</n-descriptions-item>
<n-descriptions-item label="IP归属地">{{ data.cityLabel }}</n-descriptions-item>
<n-descriptions-item label="链路ID">{{ data.reqId }}</n-descriptions-item>
<n-descriptions-item label="请求耗时">{{ data.takeUpTime }} ms</n-descriptions-item>
<n-descriptions-item label="响应时间">{{
timestampToTime(data.timestamp)
data.timestamp > 0 ? timestampToTime(data.timestamp) : '--'
}}</n-descriptions-item>
<n-descriptions-item label="创建时间">{{ data.createdAt }}</n-descriptions-item>
<n-descriptions-item label="访问时间">{{ data.createdAt }}</n-descriptions-item>
</n-descriptions>
</n-card>
<n-card
@ -131,22 +134,13 @@
const message = useMessage();
const router = useRouter();
const logId = Number(router.currentRoute.value.params.id);
const params = router.currentRoute.value.params;
const loading = ref(false);
onMounted(() => {
if (logId === undefined || logId < 1) {
message.error('ID不正确请检查');
return;
}
getInfo();
});
const data = ref({});
const getInfo = () => {
loading.value = true;
View({ id: logId })
View(params)
.then((res) => {
data.value = res;
})
@ -154,6 +148,14 @@
loading.value = false;
});
};
onMounted(() => {
if (!params.id) {
message.error('日志ID不正确请检查');
return;
}
getInfo();
});
</script>
<style lang="less" scoped>

View File

@ -77,7 +77,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
cssTarget: 'chrome80',
outDir: OUTPUT_DIR,
reportCompressedSize: false,
chunkSizeWarningLimit: 2000,
chunkSizeWarningLimit: 3000,
},
};
};