Browse Source

feat: 网站日志支持读取 10M 以上文件 (#2072)

pull/2089/head
zhengkunwang 1 year ago committed by GitHub
parent
commit
9f0d957145
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      backend/app/dto/request/website.go
  2. 1
      backend/app/dto/response/website.go
  3. 13
      backend/app/service/website.go
  4. 34
      backend/utils/files/utils.go
  5. 3
      frontend/src/api/interface/website.ts
  6. 77
      frontend/src/views/website/website/config/log/log-fiile/index.vue

8
backend/app/dto/request/website.go

@ -141,9 +141,11 @@ type WebsiteNginxUpdate struct {
}
type WebsiteLogReq struct {
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
LogType string `json:"logType" validate:"required"`
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
LogType string `json:"logType" validate:"required"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
type WebsiteDefaultUpdate struct {

1
backend/app/dto/response/website.go

@ -41,6 +41,7 @@ type WebsiteHTTPS struct {
type WebsiteLog struct {
Enable bool `json:"enable"`
Content string `json:"content"`
End bool `json:"end"`
}
type PHPConfig struct {

13
backend/app/service/website.go

@ -931,19 +931,12 @@ func (w WebsiteService) OpWebsiteLog(req request.WebsiteLogReq) (*response.Websi
}
}
filePath := path.Join(sitePath, "log", req.LogType)
fileInfo, err := os.Stat(filePath)
lines, end, err := files.ReadFileByLine(filePath, req.Page, req.PageSize)
if err != nil {
return nil, err
}
if fileInfo.Size() > 20<<20 {
return nil, buserr.New(constant.ErrFileToLarge)
}
fileInfo.Size()
content, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
res.Content = string(content)
res.End = end
res.Content = strings.Join(lines, "\n")
return res, nil
case constant.DisableLog:
key := "access_log"

34
backend/utils/files/utils.go

@ -1,6 +1,7 @@
package files
import (
"bufio"
"github.com/spf13/afero"
"net/http"
"os"
@ -75,3 +76,36 @@ const dotCharacter = 46
func IsHidden(path string) bool {
return path[0] == dotCharacter
}
func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error) {
file, err := os.Open(filename)
if err != nil {
return nil, false, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
var lines []string
currentLine := 0
startLine := (page - 1) * pageSize
endLine := startLine + pageSize
for scanner.Scan() {
if currentLine >= startLine && currentLine < endLine {
lines = append(lines, scanner.Text())
}
currentLine++
if currentLine >= endLine {
break
}
}
if err := scanner.Err(); err != nil {
return nil, false, err
}
isEndOfFile := currentLine < endLine
return lines, isEndOfFile, nil
}

3
frontend/src/api/interface/website.ts

@ -82,11 +82,14 @@ export namespace Website {
id: number;
operate: string;
logType: string;
page?: number;
pageSize?: number;
}
export interface WebSiteLog {
enable: boolean;
content: string;
end: boolean;
}
export interface Domain {

77
frontend/src/views/website/website/config/log/log-fiile/index.vue

@ -23,6 +23,7 @@
</div>
<br />
<codemirror
re="logContainer"
style="height: calc(100vh - 430px); width: 100%"
:autofocus="true"
:placeholder="$t('website.noLog')"
@ -33,7 +34,7 @@
theme="cobalt"
:styleActiveLine="true"
:extensions="extensions"
v-model="data.content"
v-model="content"
:disabled="true"
@ready="handleReady"
/>
@ -43,7 +44,7 @@
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
import { computed, nextTick, onMounted, onUnmounted, ref, shallowRef } from 'vue';
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, shallowRef } from 'vue';
import { OpWebsiteLog } from '@/api/modules/website';
import { dateFormatForName, downloadWithContent } from '@/utils/util';
import { useDeleteData } from '@/hooks/use-delete-data';
@ -74,31 +75,52 @@ const tailLog = ref(false);
let timer: NodeJS.Timer | null = null;
const view = shallowRef();
const editorContainer = ref<HTMLDivElement | null>(null);
const handleReady = (payload) => {
view.value = payload.view;
editorContainer.value = payload.container;
};
const content = ref('');
const end = ref(false);
const lastContent = ref('');
const readReq = reactive({
id: id.value,
operate: 'get',
logType: logType.value,
page: 0,
pageSize: 500,
});
const getContent = () => {
const req = {
id: id.value,
operate: 'get',
logType: logType.value,
};
loading.value = true;
OpWebsiteLog(req)
.then((res) => {
data.value = res.data;
nextTick(() => {
const state = view.value.state;
view.value.dispatch({
selection: { anchor: state.doc.length, head: state.doc.length },
scrollIntoView: true,
});
if (!end.value) {
readReq.page += 1;
}
OpWebsiteLog(readReq).then((res) => {
if (!end.value && res.data.end) {
lastContent.value = content.value;
}
data.value = res.data;
if (res.data.content != '') {
if (end.value) {
content.value = lastContent.value + '\n' + res.data.content;
} else {
if (content.value == '') {
content.value = res.data.content;
} else {
content.value = content.value + '\n' + res.data.content;
}
}
}
end.value = res.data.end;
nextTick(() => {
const state = view.value.state;
view.value.dispatch({
selection: { anchor: state.doc.length, head: state.doc.length },
});
})
.finally(() => {
loading.value = false;
view.value.focus();
});
});
};
const changeTail = () => {
@ -152,8 +174,23 @@ const onCloseLog = async () => {
timer = null;
};
function isScrolledToBottom(element: HTMLElement): boolean {
return element.scrollTop + element.clientHeight === element.scrollHeight;
}
onMounted(() => {
getContent();
nextTick(() => {
let editorElement = editorContainer.value.querySelector('.cm-editor');
let scrollerElement = editorElement.querySelector('.cm-scroller') as HTMLElement;
if (scrollerElement) {
scrollerElement.addEventListener('scroll', function () {
if (isScrolledToBottom(scrollerElement)) {
getContent();
}
});
}
});
});
onUnmounted(() => {

Loading…
Cancel
Save