add: list and download log&cdr export file

This commit is contained in:
2024-08-27 20:11:47 +08:00
parent 74874ccad5
commit eec27d039a
11 changed files with 400 additions and 2 deletions

View File

@@ -667,6 +667,10 @@ INSERT INTO `sys_dict_data` VALUES (2156, 2156, 'job.ne_config_backup_remark', '
INSERT INTO `sys_dict_data` VALUES (2157, 2157, 'job.exportOperateLog', '定期从操作日志表导出文件到指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (2158, 2158, 'job.exportIMSCDR', '定期从语音话单表导出文件至指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (2159, 2159, 'job.exportSMFCDR', '定期从数据话单表导出文件至指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (2160, 2160, 'table.sys_log_operate', '操作日志', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (2161, 2161, 'table.cdr_event_ims', '语音话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (2162, 2162, 'table.cdr_event_smf', '数据话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (2163, 2163, 'table.cdr_event_smsc', '短信话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
-- 多租户
INSERT INTO `sys_dict_data` VALUES (11000, 11000, 'menu.security.tenant', '租户管理', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1700000000000, NULL, 0, NULL);

View File

@@ -667,6 +667,10 @@ INSERT INTO `sys_dict_data` VALUES (4156, 4156, 'job.ne_config_backup_remark', '
INSERT INTO `sys_dict_data` VALUES (4157, 4157, 'job.exportOperateLog', 'Export regularly from operation log table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (4158, 4158, 'job.exportIMSCDR', 'Export regularly from IMS CDR table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (4159, 4159, 'job.exportSMFCDR', 'Export regularly from SMF CDR table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (4160, 4160, 'table.sys_log_operate', 'Operation Log', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (4161, 4161, 'table.cdr_event_ims', 'Voice CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (4162, 4162, 'table.cdr_event_smf', 'Data CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
INSERT INTO `sys_dict_data` VALUES (4163, 4163, 'table.cdr_event_smsc', 'SMS CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
-- 多租户
INSERT INTO `sys_dict_data` VALUES (14000, 14000, 'menu.security.tenant', 'Tenant Management', 'i18n_en', '', '', '1', 'supervisor', 1705550000000, '', 0, '');

View File

@@ -674,7 +674,10 @@ REPLACE INTO `sys_dict_data` VALUES (2156, 2156, 'job.ne_config_backup_remark',
REPLACE INTO `sys_dict_data` VALUES (2157, 2157, 'job.exportOperateLog', '定期从操作日志表导出文件到指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (2158, 2158, 'job.exportIMSCDR', '定期从语音话单表导出文件至指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (2159, 2159, 'job.exportSMFCDR', '定期从数据话单表导出文件至指定目录', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (2160, 2160, 'table.sys_log_operate', '操作日志', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (2161, 2161, 'table.cdr_event_ims', '语音话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (2162, 2162, 'table.cdr_event_smf', '数据话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (2163, 2163, 'table.cdr_event_smsc', '短信话单', 'i18n_zh', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
-- multi-tenancy
REPLACE INTO `sys_dict_data` VALUES (11000, 11000, 'menu.security.tenant', '租户管理', 'i18n_zh', NULL, NULL, '1', 'supervisor', 1700000000000, NULL, 0, NULL);

View File

@@ -669,6 +669,10 @@ REPLACE INTO `sys_dict_data` VALUES (4156, 4156, 'job.ne_config_backup_remark',
REPLACE INTO `sys_dict_data` VALUES (4157, 4157, 'job.exportOperateLog', 'Export regularly from operation log table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (4158, 4158, 'job.exportIMSCDR', 'Export regularly from IMS CDR table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (4159, 4159, 'job.exportSMFCDR', 'Export regularly from SMF CDR table', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (4160, 4160, 'table.sys_log_operate', 'Operation Log', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (4161, 4161, 'table.cdr_event_ims', 'Voice CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (4162, 4162, 'table.cdr_event_smf', 'Data CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
REPLACE INTO `sys_dict_data` VALUES (4163, 4163, 'table.cdr_event_smsc', 'SMS CDR', 'i18n_en', '', '', '1', 'supervisor', 1721902269805, '', 0, '');
-- 多租户

View File

@@ -1,6 +1,7 @@
package features
import (
"be.ems/features/lm"
"be.ems/features/pm"
"be.ems/lib/log"
"github.com/gin-gonic/gin"
@@ -10,8 +11,9 @@ func InitServiceEngine(r *gin.Engine) {
log.Info("======init feature group gin.Engine")
// featuresGroup := r.Group("/")
// 注册 PM 模块的路由
// 注册 各个features 模块的路由
pm.InitSubServiceRoute(r)
lm.InitSubServiceRoute(r)
// return featuresGroup
}

View File

@@ -0,0 +1,124 @@
package file_export
import (
"encoding/json"
"net/http"
"os"
"path/filepath"
"be.ems/lib/file"
"be.ems/lib/log"
"be.ems/lib/services"
"be.ems/src/framework/datasource"
"be.ems/src/framework/i18n"
"be.ems/src/framework/utils/ctx"
"github.com/gin-gonic/gin"
)
type SysJobResponse struct {
SysJob
TableName string `json:"tableName"`
TableDisplay string `json:"tableDisplay"`
FilePath string `json:"filePath"`
}
type TargetParams struct {
Duration int `json:"duration"`
TableName string `json:"tableName"`
Columns string `json:"columns"` // exported column name of time string
TimeCol string `json:"timeCol"` // time stamp of column name
TimeUnit string `json:"timeUnit"` // timestamp unit: second/micro/milli
Extras string `json:"extras"` // extras condition for where
FilePath string `json:"filePath"` // file path
}
func (m *SysJob) GetFileExportTable(c *gin.Context) {
var results []SysJob
err := datasource.DefaultDB().Table(m.TableName()).Where("invoke_target=? and status=1", INVOKE_FILE_EXPORT).
Find(&results).Error
if err != nil {
c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error()))
return
}
language := ctx.AcceptLanguage(c)
var response []SysJobResponse
for _, job := range results {
var params TargetParams
if err := json.Unmarshal(job.TargetParams, &params); err != nil {
c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error()))
return
}
TableDisplay := i18n.TKey(language, "table."+params.TableName)
if TableDisplay == "" {
TableDisplay = params.TableName
}
response = append(response, SysJobResponse{
SysJob: job,
TableName: params.TableName,
TableDisplay: TableDisplay,
FilePath: params.FilePath,
})
}
c.JSON(http.StatusOK, services.DataResp(response))
}
func (m *FileExport) GetFileList(c *gin.Context) {
dir := c.Query("path")
suffix := c.Query("suffix")
files, err := file.GetFileInfo(dir, suffix)
if err != nil {
log.Error(err)
c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error()))
}
total := int64(len(files))
c.JSON(http.StatusOK, services.TotalDataResp(files, total))
}
func (m *FileExport) Total(c *gin.Context) {
dir := c.Query("path")
fileCount, dirCount, err := file.GetFileAndDirCount(dir)
if err != nil {
log.Error(err)
c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error()))
return
}
total := fileCount + dirCount
c.JSON(http.StatusOK, services.TotalResp(int64(total)))
}
func (m *FileExport) DownloadHandler(c *gin.Context) {
dir := c.Query("path")
fileName := c.Param("fileName")
filePath := filepath.Join(dir, fileName)
file, err := os.Open(filePath)
if err != nil {
c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error()))
return
}
defer file.Close()
if _, err := os.Stat(filePath); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, services.ErrResp(err.Error()))
return
}
c.Header("Content-Disposition", "attachment; filename="+fileName)
c.Header("Content-Type", "application/octet-stream")
c.File(filePath)
}
func (m *FileExport) Delete(c *gin.Context) {
fileName := c.Param("fileName")
dir := c.Query("path")
filePath := filepath.Join(dir, fileName)
if err := os.Remove(filePath); err != nil {
c.JSON(http.StatusInternalServerError, services.ErrResp(err.Error()))
return
}
c.JSON(http.StatusNoContent, nil) // 204 No Content
}

View File

@@ -0,0 +1,34 @@
package file_export
import (
"gorm.io/datatypes"
)
const (
INVOKE_FILE_EXPORT = "exportTable"
)
type SysJob struct {
JobID int64 `gorm:"column:job_id;primary_key;auto_increment" json:"job_id"` //任务ID
InvokeTarget string `gorm:"column:invoke_target" json:"invoke_target"` //调用目标字符串
TargetParams datatypes.JSON `gorm:"column:target_params;type:json" json:"target_params,omitempty"` //调用目标传入参数
}
func (m *SysJob) TableName() string {
return "sys_job"
}
type FileExport struct {
FileType string `json:"fileType"` // 文件类型
FileMode string `json:"fileMode"` // 文件的权限
LinkCount int64 `json:"linkCount"` // 硬链接数目
Owner string `json:"owner"` // 所属用户
Group string `json:"group"` // 所属组
Size int64 `json:"size"` // 文件的大小
ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒
FileName string `json:"fileName"` // 文件的名称
}
func (m *FileExport) TableName() string {
return "file_export"
}

View File

@@ -0,0 +1,40 @@
package file_export
import (
"be.ems/src/framework/middleware"
"github.com/gin-gonic/gin"
)
// Register Routes for file_export
func Register(r *gin.RouterGroup) {
lmTable := r.Group("/table")
{
var m *SysJob
lmTable.GET("/list",
middleware.PreAuthorize(nil),
m.GetFileExportTable,
)
}
lmFile := r.Group("/file")
{
var f *FileExport
lmFile.GET("/list",
middleware.PreAuthorize(nil),
f.GetFileList,
)
lmFile.GET("/total",
middleware.PreAuthorize(nil),
f.Total,
)
lmFile.GET("/:fileName",
middleware.PreAuthorize(nil),
f.DownloadHandler,
)
lmFile.DELETE("/:fileName",
middleware.PreAuthorize(nil),
f.Delete,
)
}
}

17
features/lm/service.go Normal file
View File

@@ -0,0 +1,17 @@
// log management package
package lm
import (
"be.ems/features/lm/file_export"
"be.ems/lib/log"
"github.com/gin-gonic/gin"
)
func InitSubServiceRoute(r *gin.Engine) {
log.Info("======init Log management group gin.Engine")
lmGroup := r.Group("/lm")
// register sub modules routes
file_export.Register(lmGroup)
}

84
lib/file/file_linux.go Normal file
View File

@@ -0,0 +1,84 @@
//go:build linux
// +build linux
package file
import (
"fmt"
"os"
"path/filepath"
"syscall"
)
type FileInfo struct {
FileType string `json:"fileType"` // 文件类型
FileMode string `json:"fileMode"` // 文件的权限
LinkCount int64 `json:"linkCount"` // 硬链接数目
Owner string `json:"owner"` // 所属用户
Group string `json:"group"` // 所属组
Size int64 `json:"size"` // 文件的大小
ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒
FileName string `json:"fileName"` // 文件的名称
}
func GetFileInfo(dir, suffix string) ([]FileInfo, error) {
var files []FileInfo
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == dir {
return nil // 跳过当前目录
}
fileType := "file"
if info.IsDir() {
fileType = "directory"
} else if info.Mode()&os.ModeSymlink != 0 {
fileType = "symlink"
}
// check if match suffix
if (suffix != "" && filepath.Ext(path) == suffix) || suffix == "" {
fileInfo := FileInfo{
FileType: fileType,
FileMode: info.Mode().String(),
LinkCount: int64(info.Sys().(*syscall.Stat_t).Nlink),
Owner: fmt.Sprintf("%d", info.Sys().(*syscall.Stat_t).Uid),
Group: fmt.Sprintf("%d", info.Sys().(*syscall.Stat_t).Gid),
Size: info.Size(),
ModifiedTime: info.ModTime().Unix(),
FileName: info.Name(),
}
files = append(files, fileInfo)
}
return nil
})
if err != nil {
return nil, err
}
return files, nil
}
func GetFileAndDirCount(dir string) (int, int, error) {
var fileCount, dirCount int
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == dir {
return nil // 跳过当前目录
}
if info.IsDir() {
dirCount++
} else {
fileCount++
}
return nil
})
return fileCount, dirCount, err
}

82
lib/file/file_windows.go Normal file
View File

@@ -0,0 +1,82 @@
//go:build windows
// +build windows
package file
import (
"os"
"path/filepath"
)
type FileInfo struct {
FileType string `json:"fileType"` // 文件类型
FileMode string `json:"fileMode"` // 文件的权限
LinkCount int64 `json:"linkCount"` // 硬链接数目
Owner string `json:"owner"` // 所属用户
Group string `json:"group"` // 所属组
Size int64 `json:"size"` // 文件的大小
ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒
FileName string `json:"fileName"` // 文件的名称
}
func GetFileInfo(dir, suffix string) ([]FileInfo, error) {
var files []FileInfo
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == dir {
return nil // 跳过当前目录
}
fileType := "file"
if info.IsDir() {
fileType = "directory"
} else if info.Mode()&os.ModeSymlink != 0 {
fileType = "symlink"
}
// check if match suffix
if (suffix != "" && filepath.Ext(path) == suffix) || suffix == "" {
fileInfo := FileInfo{
FileType: fileType,
FileMode: info.Mode().String(),
LinkCount: 0,
Owner: "N/A",
Group: "N/A",
Size: info.Size(),
ModifiedTime: info.ModTime().Unix(),
FileName: info.Name(),
}
files = append(files, fileInfo)
}
return nil
})
if err != nil {
return nil, err
}
return files, nil
}
func GetFileAndDirCount(dir string) (int, int, error) {
var fileCount, dirCount int
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == dir {
return nil // 跳过当前目录
}
if info.IsDir() {
dirCount++
} else {
fileCount++
}
return nil
})
return fileCount, dirCount, err
}