数据结构优化
This commit is contained in:
@@ -21,6 +21,14 @@
|
||||
>
|
||||
思考
|
||||
</button>
|
||||
<button
|
||||
@click="goTo('/columns')"
|
||||
class="nav-item text-sm font-medium tracking-wide text-art-muted hover:text-white transition-colors link-underline"
|
||||
:class="{ 'text-white after:w-full': activeNav === 'columns' }"
|
||||
data-target="columns"
|
||||
>
|
||||
专栏
|
||||
</button>
|
||||
<button
|
||||
@click="goTo('/works')"
|
||||
class="nav-item text-sm font-medium tracking-wide text-art-muted hover:text-white transition-colors link-underline"
|
||||
|
||||
159
client/src/components/ImageUploader.vue
Normal file
159
client/src/components/ImageUploader.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div class="image-uploader">
|
||||
<div
|
||||
v-if="!imageUrl"
|
||||
@click="triggerFileInput"
|
||||
@dragover.prevent
|
||||
@dragenter.prevent
|
||||
@drop.prevent="handleDrop"
|
||||
class="upload-area border-2 border-dashed border-white/20 rounded-lg p-8 text-center cursor-pointer hover:border-art-accent transition-colors"
|
||||
:class="{ 'border-art-accent': isDragging }"
|
||||
>
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
@change="handleFileSelect"
|
||||
class="hidden"
|
||||
/>
|
||||
<div class="space-y-2">
|
||||
<i data-lucide="upload" class="w-12 h-12 mx-auto text-art-muted"></i>
|
||||
<p class="text-sm text-art-muted">点击或拖拽图片到此处上传</p>
|
||||
<p class="text-xs text-art-muted/50">支持 JPG、PNG、GIF 格式</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="image-preview relative group">
|
||||
<img :src="imageUrl" alt="Preview" class="w-full h-auto rounded-lg" />
|
||||
<div class="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity rounded-lg flex items-center justify-center gap-2">
|
||||
<button
|
||||
@click="removeImage"
|
||||
class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
<button
|
||||
@click="triggerFileInput"
|
||||
class="px-4 py-2 bg-art-accent text-white rounded hover:bg-opacity-80 transition-colors"
|
||||
>
|
||||
更换
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="uploading" class="mt-4 text-center">
|
||||
<div class="inline-flex items-center gap-2 text-art-accent">
|
||||
<div class="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-art-accent"></div>
|
||||
<span class="text-sm">上传中...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="mt-4 text-center text-red-500 text-sm">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useToast } from '../composables/useToast'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: string
|
||||
categoryId?: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
const imageUrl = ref<string>(props.modelValue || '')
|
||||
const uploading = ref(false)
|
||||
const error = ref('')
|
||||
const isDragging = ref(false)
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
imageUrl.value = newVal || ''
|
||||
})
|
||||
|
||||
const triggerFileInput = () => {
|
||||
fileInput.value?.click()
|
||||
}
|
||||
|
||||
const handleFileSelect = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement
|
||||
if (target.files && target.files[0]) {
|
||||
uploadFile(target.files[0])
|
||||
}
|
||||
}
|
||||
|
||||
const handleDrop = (e: DragEvent) => {
|
||||
isDragging.value = false
|
||||
if (e.dataTransfer?.files && e.dataTransfer.files[0]) {
|
||||
uploadFile(e.dataTransfer.files[0])
|
||||
}
|
||||
}
|
||||
|
||||
const uploadFile = async (file: File) => {
|
||||
if (!file.type.startsWith('image/')) {
|
||||
error.value = '请选择图片文件'
|
||||
return
|
||||
}
|
||||
|
||||
uploading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
if (props.categoryId) {
|
||||
formData.append('categoryId', props.categoryId.toString())
|
||||
}
|
||||
formData.append('storageType', 'local')
|
||||
|
||||
const token = localStorage.getItem('token')
|
||||
const response = await fetch('http://localhost:8081/api/admin/attachments/upload', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {})
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.message || '上传失败')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
imageUrl.value = data.result.fileUrl
|
||||
emit('update:modelValue', data.result.fileUrl)
|
||||
toast.showToast('图片上传成功', 'success')
|
||||
} catch (err: any) {
|
||||
error.value = err.message || '上传失败,请重试'
|
||||
toast.showToast(error.value, 'error')
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const removeImage = () => {
|
||||
imageUrl.value = ''
|
||||
emit('update:modelValue', '')
|
||||
if (fileInput.value) {
|
||||
fileInput.value.value = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.upload-area {
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@@ -240,6 +240,7 @@ const menuItems = ref<MenuItem[]>([
|
||||
isOpen: false,
|
||||
children: [
|
||||
{ title: '全局配置', path: '/admin/settings', icon: '🛠️' },
|
||||
{ title: '附件管理', path: '/admin/attachments', icon: '📎' },
|
||||
{ title: '操作日志', path: '/admin/logs', icon: '📋' }
|
||||
]
|
||||
}
|
||||
|
||||
347
client/src/components/admin/PostRelationModal.vue
Normal file
347
client/src/components/admin/PostRelationModal.vue
Normal file
@@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm" @click.self="close">
|
||||
<div class="bg-[#1a1a1a] border border-white/10 rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] overflow-hidden animate-reveal flex flex-col">
|
||||
<div class="p-4 border-b border-white/10 flex justify-between items-center shrink-0">
|
||||
<h3 class="text-lg font-serif italic text-white">管理文章关联</h3>
|
||||
<button @click="close" class="text-white/50 hover:text-white">
|
||||
<i data-lucide="x" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6 space-y-6 overflow-y-auto flex-1">
|
||||
<div>
|
||||
<p class="text-sm text-art-muted">文章: <span class="text-white font-medium">{{ post?.title }}</span></p>
|
||||
</div>
|
||||
|
||||
<!-- Category Selection -->
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="block text-sm font-medium text-white">分类</label>
|
||||
<button @click="showCreateCategory = true" class="text-xs text-art-accent hover:text-white transition-colors">
|
||||
+ 新建分类
|
||||
</button>
|
||||
</div>
|
||||
<select v-model="selectedCategoryId" class="admin-input">
|
||||
<option :value="0">请选择分类</option>
|
||||
<option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
|
||||
</select>
|
||||
|
||||
<!-- Quick Create Category -->
|
||||
<div v-if="showCreateCategory" class="admin-card p-3 space-y-2">
|
||||
<input
|
||||
v-model="newCategory.name"
|
||||
placeholder="分类名称"
|
||||
class="admin-input text-sm"
|
||||
@keyup.enter="createCategory"
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<button @click="createCategory" class="admin-btn-primary text-xs px-3 py-1">创建</button>
|
||||
<button @click="showCreateCategory = false" class="admin-btn-secondary text-xs px-3 py-1">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Column Selection -->
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="block text-sm font-medium text-white">专栏 <span class="text-art-muted text-xs">(可选)</span></label>
|
||||
<button @click="showCreateColumn = true" class="text-xs text-art-accent hover:text-white transition-colors">
|
||||
+ 新建专栏
|
||||
</button>
|
||||
</div>
|
||||
<select v-model="selectedColumnId" class="admin-input">
|
||||
<option :value="0">不选择专栏</option>
|
||||
<option v-for="col in columns" :key="col.id" :value="col.id">{{ col.name }}</option>
|
||||
</select>
|
||||
|
||||
<!-- Quick Create Column -->
|
||||
<div v-if="showCreateColumn" class="admin-card p-3 space-y-2">
|
||||
<input
|
||||
v-model="newColumn.name"
|
||||
placeholder="专栏名称"
|
||||
class="admin-input text-sm"
|
||||
@keyup.enter="createColumn"
|
||||
/>
|
||||
<input
|
||||
v-model="newColumn.cover"
|
||||
placeholder="封面图片URL"
|
||||
class="admin-input text-sm"
|
||||
/>
|
||||
<textarea
|
||||
v-model="newColumn.description"
|
||||
placeholder="描述"
|
||||
rows="2"
|
||||
class="admin-input text-sm"
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<button @click="createColumn" class="admin-btn-primary text-xs px-3 py-1">创建</button>
|
||||
<button @click="showCreateColumn = false" class="admin-btn-secondary text-xs px-3 py-1">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tags Selection -->
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="block text-sm font-medium text-white">标签 <span class="text-art-muted text-xs">(可多选)</span></label>
|
||||
<button @click="showCreateTag = true" class="text-xs text-art-accent hover:text-white transition-colors">
|
||||
+ 新建标签
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 min-h-[60px] p-3 bg-white/5 rounded border border-white/10">
|
||||
<button
|
||||
v-for="tag in tags"
|
||||
:key="tag.id"
|
||||
@click="toggleTag(tag.id)"
|
||||
class="px-3 py-1 text-sm rounded border transition-colors"
|
||||
:class="selectedTagIds.includes(tag.id)
|
||||
? 'bg-art-accent/20 border-art-accent text-art-accent'
|
||||
: 'bg-white/5 border-white/10 text-art-muted hover:border-white/30'"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</button>
|
||||
<div v-if="tags.length === 0" class="text-xs text-art-muted italic">
|
||||
暂无标签
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Create Tag -->
|
||||
<div v-if="showCreateTag" class="admin-card p-3 space-y-2">
|
||||
<input
|
||||
v-model="newTag.name"
|
||||
placeholder="标签名称"
|
||||
class="admin-input text-sm"
|
||||
@keyup.enter="createTag"
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<button @click="createTag" class="admin-btn-primary text-xs px-3 py-1">创建</button>
|
||||
<button @click="showCreateTag = false" class="admin-btn-secondary text-xs px-3 py-1">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border-t border-white/10 flex justify-end gap-3 bg-white/5 shrink-0">
|
||||
<button @click="close" class="px-4 py-2 text-sm text-white/70 hover:text-white transition-colors">取消</button>
|
||||
<button @click="save" :disabled="saving" class="admin-btn-primary py-1.5 px-4 text-sm">
|
||||
{{ saving ? '保存中...' : '保存' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import {
|
||||
fetchCategories,
|
||||
fetchColumns,
|
||||
fetchTags,
|
||||
createCategory as createCategoryApi,
|
||||
createColumn as createColumnApi,
|
||||
createTag as createTagApi,
|
||||
updatePost,
|
||||
Category,
|
||||
Column,
|
||||
Tag,
|
||||
Post
|
||||
} from '../../services/api'
|
||||
import { useToast } from '../../composables/useToast'
|
||||
|
||||
const props = defineProps<{
|
||||
isOpen: boolean
|
||||
post: Post | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:isOpen': [value: boolean]
|
||||
'saved': []
|
||||
}>()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const categories = ref<Category[]>([])
|
||||
const columns = ref<Column[]>([])
|
||||
const tags = ref<Tag[]>([])
|
||||
|
||||
const selectedCategoryId = ref(0)
|
||||
const selectedColumnId = ref(0)
|
||||
const selectedTagIds = ref<number[]>([])
|
||||
|
||||
const showCreateCategory = ref(false)
|
||||
const showCreateColumn = ref(false)
|
||||
const showCreateTag = ref(false)
|
||||
|
||||
const newCategory = ref({ name: '', slug: '', description: '' })
|
||||
const newColumn = ref({ name: '', cover: '', description: '', isActive: 1, sortOrder: 0 })
|
||||
const newTag = ref({ name: '', slug: '' })
|
||||
|
||||
const saving = ref(false)
|
||||
|
||||
const close = () => {
|
||||
emit('update:isOpen', false)
|
||||
showCreateCategory.value = false
|
||||
showCreateColumn.value = false
|
||||
showCreateTag.value = false
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const [cats, cols, tagList] = await Promise.all([
|
||||
fetchCategories(),
|
||||
fetchColumns(),
|
||||
fetchTags()
|
||||
])
|
||||
categories.value = cats
|
||||
columns.value = cols
|
||||
tags.value = tagList
|
||||
} catch (err: any) {
|
||||
toast.showToast('加载数据失败', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const initForm = () => {
|
||||
if (props.post) {
|
||||
selectedCategoryId.value = props.post.categoryId || 0
|
||||
selectedColumnId.value = props.post.columnId || 0
|
||||
selectedTagIds.value = props.post.tags?.map(t => t.id) || []
|
||||
}
|
||||
}
|
||||
|
||||
const toggleTag = (tagId: number) => {
|
||||
const index = selectedTagIds.value.indexOf(tagId)
|
||||
if (index === -1) {
|
||||
selectedTagIds.value.push(tagId)
|
||||
} else {
|
||||
selectedTagIds.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const createCategory = async () => {
|
||||
if (!newCategory.value.name) {
|
||||
toast.showToast('请输入分类名称', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await createCategoryApi({
|
||||
name: newCategory.value.name,
|
||||
slug: newCategory.value.slug || newCategory.value.name,
|
||||
description: newCategory.value.description || ''
|
||||
})
|
||||
toast.showToast('分类创建成功', 'success')
|
||||
const createdName = newCategory.value.name
|
||||
newCategory.value = { name: '', slug: '', description: '' }
|
||||
showCreateCategory.value = false
|
||||
await loadData()
|
||||
// 自动选择新创建的分类
|
||||
const newCat = categories.value.find(c => c.name === createdName)
|
||||
if (newCat) {
|
||||
selectedCategoryId.value = newCat.id
|
||||
}
|
||||
} catch (err: any) {
|
||||
toast.showToast(err.message || '创建失败', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const createColumn = async () => {
|
||||
if (!newColumn.value.name) {
|
||||
toast.showToast('请输入专栏名称', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await createColumnApi(newColumn.value)
|
||||
toast.showToast('专栏创建成功', 'success')
|
||||
const createdName = newColumn.value.name
|
||||
newColumn.value = { name: '', cover: '', description: '', isActive: 1, sortOrder: 0 }
|
||||
showCreateColumn.value = false
|
||||
await loadData()
|
||||
// 自动选择新创建的专栏
|
||||
const newCol = columns.value.find(c => c.name === createdName)
|
||||
if (newCol) {
|
||||
selectedColumnId.value = newCol.id
|
||||
}
|
||||
} catch (err: any) {
|
||||
toast.showToast(err.message || '创建失败', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const createTag = async () => {
|
||||
if (!newTag.value.name) {
|
||||
toast.showToast('请输入标签名称', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await createTagApi({
|
||||
name: newTag.value.name,
|
||||
slug: newTag.value.slug || newTag.value.name
|
||||
})
|
||||
toast.showToast('标签创建成功', 'success')
|
||||
const createdName = newTag.value.name
|
||||
newTag.value = { name: '', slug: '' }
|
||||
showCreateTag.value = false
|
||||
await loadData()
|
||||
// 自动选择新创建的标签
|
||||
const newTagItem = tags.value.find(t => t.name === createdName)
|
||||
if (newTagItem && !selectedTagIds.value.includes(newTagItem.id)) {
|
||||
selectedTagIds.value.push(newTagItem.id)
|
||||
}
|
||||
} catch (err: any) {
|
||||
toast.showToast(err.message || '创建失败', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
if (!props.post) return
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
const payload: any = {
|
||||
title: props.post.title,
|
||||
categoryId: selectedCategoryId.value,
|
||||
date: props.post.date,
|
||||
excerpt: props.post.excerpt || '',
|
||||
content: props.post.content || '',
|
||||
isPublished: props.post.isPublished || 1,
|
||||
tags: selectedTagIds.value.map(id => ({ id }))
|
||||
}
|
||||
|
||||
if (selectedColumnId.value > 0) {
|
||||
payload.columnId = selectedColumnId.value
|
||||
} else {
|
||||
payload.columnId = null
|
||||
}
|
||||
|
||||
await updatePost(props.post.id, payload)
|
||||
toast.showToast('保存成功', 'success')
|
||||
emit('saved')
|
||||
close()
|
||||
} catch (err: any) {
|
||||
toast.showToast(err.message || '保存失败', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.isOpen, (open) => {
|
||||
if (open) {
|
||||
loadData()
|
||||
initForm()
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.post, () => {
|
||||
if (props.isOpen) {
|
||||
initForm()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.isOpen) {
|
||||
loadData()
|
||||
initForm()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -6,7 +6,7 @@
|
||||
<p class="text-art-muted max-w-lg mx-auto mb-8">关于前端技术、交互设计以及数字艺术的深度思考。</p>
|
||||
|
||||
<!-- Search UI -->
|
||||
<div class="max-w-md mx-auto relative">
|
||||
<div class="max-w-md mx-auto relative mb-6">
|
||||
<input
|
||||
type="text"
|
||||
v-model="searchQuery"
|
||||
@@ -28,6 +28,41 @@
|
||||
<i data-lucide="search" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filter UI -->
|
||||
<div class="max-w-2xl mx-auto flex flex-wrap gap-4 justify-center items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-art-muted">分类:</label>
|
||||
<div class="w-40">
|
||||
<CustomSelect
|
||||
v-model="selectedCategoryId"
|
||||
:options="categoryOptions"
|
||||
placeholder="全部"
|
||||
@update:modelValue="applyFilters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-art-muted">标签:</label>
|
||||
<div class="w-40">
|
||||
<CustomSelect
|
||||
v-model="selectedTagId"
|
||||
:options="tagOptions"
|
||||
placeholder="全部"
|
||||
@update:modelValue="applyFilters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="hasActiveFilters"
|
||||
@click="clearFilters"
|
||||
class="text-xs text-art-accent hover:text-white transition-colors px-3 py-1 border border-art-accent/30 rounded hover:border-art-accent"
|
||||
>
|
||||
清除筛选
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
@@ -83,29 +118,58 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { fetchPosts, Post } from '../services/api'
|
||||
import { ref, onMounted, nextTick, computed } from 'vue'
|
||||
import { fetchPosts, fetchCategories, fetchTags, Post, Category, Tag } from '../services/api'
|
||||
import { useScrollAnimation } from '../composables/useScrollAnimation'
|
||||
import CustomSelect from '../components/CustomSelect.vue'
|
||||
|
||||
const blogPosts = ref<Post[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const searchQuery = ref('')
|
||||
const isSearching = ref(false)
|
||||
const selectedCategoryId = ref(0)
|
||||
const selectedTagId = ref(0)
|
||||
const categories = ref<Category[]>([])
|
||||
const tags = ref<Tag[]>([])
|
||||
|
||||
const { initObserver } = useScrollAnimation()
|
||||
|
||||
const hasActiveFilters = computed(() => {
|
||||
return selectedCategoryId.value > 0 || selectedTagId.value > 0
|
||||
})
|
||||
|
||||
// 计算属性:分类选项
|
||||
const categoryOptions = computed(() => {
|
||||
return [
|
||||
{ value: 0, label: '全部' },
|
||||
...categories.value.map(cat => ({ value: cat.id, label: cat.name }))
|
||||
]
|
||||
})
|
||||
|
||||
// 计算属性:标签选项
|
||||
const tagOptions = computed(() => {
|
||||
return [
|
||||
{ value: 0, label: '全部' },
|
||||
...tags.value.map(tag => ({ value: tag.id, label: tag.name }))
|
||||
]
|
||||
})
|
||||
|
||||
const refreshIcons = () => {
|
||||
if ((window as any).lucide) {
|
||||
(window as any).lucide.createIcons()
|
||||
}
|
||||
}
|
||||
|
||||
const fetchBlogPosts = async (query?: string) => {
|
||||
const fetchBlogPosts = async () => {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const posts = await fetchPosts(query)
|
||||
const query = searchQuery.value.trim() || undefined
|
||||
const categoryId = selectedCategoryId.value > 0 ? selectedCategoryId.value : undefined
|
||||
const tagId = selectedTagId.value > 0 ? selectedTagId.value : undefined
|
||||
|
||||
const posts = await fetchPosts(query, categoryId, tagId)
|
||||
blogPosts.value = posts
|
||||
nextTick(() => {
|
||||
initObserver()
|
||||
@@ -122,7 +186,7 @@ const fetchBlogPosts = async (query?: string) => {
|
||||
const handleSearch = () => {
|
||||
if (!searchQuery.value.trim() && !isSearching.value) return
|
||||
isSearching.value = !!searchQuery.value.trim()
|
||||
fetchBlogPosts(searchQuery.value)
|
||||
fetchBlogPosts()
|
||||
}
|
||||
|
||||
const clearSearch = () => {
|
||||
@@ -131,6 +195,29 @@ const clearSearch = () => {
|
||||
fetchBlogPosts()
|
||||
}
|
||||
|
||||
const applyFilters = () => {
|
||||
fetchBlogPosts()
|
||||
}
|
||||
|
||||
const clearFilters = () => {
|
||||
selectedCategoryId.value = 0
|
||||
selectedTagId.value = 0
|
||||
fetchBlogPosts()
|
||||
}
|
||||
|
||||
const loadFilters = async () => {
|
||||
try {
|
||||
const [cats, tagList] = await Promise.all([
|
||||
fetchCategories(),
|
||||
fetchTags()
|
||||
])
|
||||
categories.value = cats
|
||||
tags.value = tagList
|
||||
} catch (err) {
|
||||
console.error('Error loading filters:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const highlightText = (text: string | undefined) => {
|
||||
if (!text) return ''
|
||||
if (!isSearching.value || !searchQuery.value) return text
|
||||
@@ -140,6 +227,7 @@ const highlightText = (text: string | undefined) => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadFilters()
|
||||
fetchBlogPosts()
|
||||
refreshIcons()
|
||||
})
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<!-- 文章信息 -->
|
||||
<div class="border-b border-white/5 pb-10">
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<span class="px-3 py-1 border border-white/20 rounded-full text-xs font-mono text-art-accent uppercase tracking-wider">{{ post.category }}</span>
|
||||
<span class="px-3 py-1 border border-white/20 rounded-full text-xs font-mono text-art-accent uppercase tracking-wider">{{ post.categoryName || '未分类' }}</span>
|
||||
<span class="text-sm text-art-muted">{{ post.date }}</span>
|
||||
</div>
|
||||
<h1 class="font-serif text-4xl md:text-5xl lg:text-6xl text-white leading-tight mb-8">{{ post.title }}</h1>
|
||||
|
||||
@@ -11,24 +11,52 @@
|
||||
<div class="mb-16 text-center">
|
||||
<div class="inline-block mb-4 px-3 py-1 rounded-full border border-art-accent/30 text-art-accent text-xs font-mono">专栏</div>
|
||||
<h2 class="font-serif text-4xl italic text-white mb-6">{{ column.name }}</h2>
|
||||
<p class="text-art-muted max-w-lg mx-auto leading-relaxed">{{ column.description }}</p>
|
||||
<p class="text-art-muted max-w-lg mx-auto leading-relaxed mb-6">{{ column.description }}</p>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="flex items-center justify-center gap-6 text-sm text-art-muted">
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="file-text" class="w-4 h-4"></i>
|
||||
<span>{{ posts.length }} 篇文章</span>
|
||||
</div>
|
||||
<div v-if="lastUpdated" class="flex items-center gap-2">
|
||||
<i data-lucide="clock" class="w-4 h-4"></i>
|
||||
<span>最近更新:{{ formatDate(lastUpdated) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Posts List (Placeholder for now as we don't have column_posts API integrated fully yet) -->
|
||||
<!-- Since we don't have GetPostsByColumn API yet, we might need to fetch all posts and filter,
|
||||
or update backend to support fetching posts by column.
|
||||
The SQL has `column_posts` table. I need to check if backend `GetColumn` returns posts or if I need a separate endpoint.
|
||||
-->
|
||||
<!-- Posts List -->
|
||||
<div v-if="posts.length > 0" class="space-y-8">
|
||||
<article
|
||||
v-for="post in posts"
|
||||
:key="post.id"
|
||||
class="group cursor-pointer border-b border-white/5 pb-8"
|
||||
@click="router.push('/blog/' + post.id)"
|
||||
>
|
||||
<div class="flex flex-col md:flex-row gap-6 items-start">
|
||||
<div class="md:w-1/4 pt-2">
|
||||
<span class="font-mono text-xs text-art-accent block mb-1">{{ post.categoryName || '未分类' }}</span>
|
||||
<span class="text-sm text-art-muted">{{ post.date }}</span>
|
||||
<div v-if="post.tags && post.tags.length > 0" class="flex flex-wrap gap-2 mt-2">
|
||||
<span v-for="tag in post.tags" :key="tag.id" class="text-xs text-white/40 bg-white/5 px-1 rounded">#{{ tag.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md:w-3/4 space-y-3">
|
||||
<h3 class="font-serif text-2xl text-white group-hover:text-art-accent transition-colors leading-tight">
|
||||
{{ post.title }}
|
||||
</h3>
|
||||
<p class="text-art-muted font-light leading-relaxed line-clamp-2">
|
||||
{{ post.excerpt }}
|
||||
</p>
|
||||
<div class="text-xs text-art-accent font-medium mt-4 group-hover:text-white transition-colors">阅读全文 -></div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8">
|
||||
<div class="text-center py-10 border border-dashed border-white/10 rounded-lg">
|
||||
<p class="text-art-muted">专栏文章列表功能开发中...</p>
|
||||
<!--
|
||||
TODO: Fetch posts associated with this column.
|
||||
Backend needs to expose this.
|
||||
For now, I'll just show the column info.
|
||||
-->
|
||||
</div>
|
||||
<div v-else class="text-center py-10 border border-dashed border-white/10 rounded-lg">
|
||||
<p class="text-art-muted">该专栏暂无文章</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,22 +68,105 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { fetchColumn, Column } from '../services/api'
|
||||
import { ref, onMounted, nextTick, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { fetchColumn, fetchColumnPosts, Column, Post } from '../services/api'
|
||||
import { useScrollAnimation } from '../composables/useScrollAnimation'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const column = ref<Column | null>(null)
|
||||
const posts = ref<Post[]>([])
|
||||
const loading = ref(true)
|
||||
const { initObserver } = useScrollAnimation()
|
||||
|
||||
// 计算最近更新时间
|
||||
const lastUpdated = computed(() => {
|
||||
if (posts.value.length === 0) {
|
||||
// 如果没有文章,使用专栏的更新时间
|
||||
if (column.value?.updatedAt) {
|
||||
const timestamp = typeof column.value.updatedAt === 'string'
|
||||
? parseInt(column.value.updatedAt)
|
||||
: column.value.updatedAt
|
||||
return new Date(timestamp * 1000)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 找到所有文章中最新的日期
|
||||
const dates = posts.value
|
||||
.map(post => {
|
||||
// 使用 post.date,它通常是格式化的日期字符串
|
||||
if (!post.date) return null
|
||||
|
||||
// 尝试解析日期字符串
|
||||
const date = new Date(post.date)
|
||||
if (isNaN(date.getTime())) return null
|
||||
return date
|
||||
})
|
||||
.filter((date): date is Date => date !== null)
|
||||
|
||||
if (dates.length === 0) {
|
||||
// 如果无法解析文章日期,使用专栏更新时间
|
||||
if (column.value?.updatedAt) {
|
||||
const timestamp = typeof column.value.updatedAt === 'string'
|
||||
? parseInt(column.value.updatedAt)
|
||||
: column.value.updatedAt
|
||||
return new Date(timestamp * 1000)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 返回最新的日期
|
||||
return new Date(Math.max(...dates.map(d => d.getTime())))
|
||||
})
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (date: Date | null): string => {
|
||||
if (!date) return ''
|
||||
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||
|
||||
if (days === 0) return '今天'
|
||||
if (days === 1) return '昨天'
|
||||
if (days < 7) return `${days} 天前`
|
||||
if (days < 30) return `${Math.floor(days / 7)} 周前`
|
||||
if (days < 365) return `${Math.floor(days / 30)} 个月前`
|
||||
|
||||
// 超过一年,显示具体日期
|
||||
return date.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const id = parseInt(route.params.id as string)
|
||||
column.value = await fetchColumn(id)
|
||||
const [colData, postsData] = await Promise.all([
|
||||
fetchColumn(id),
|
||||
fetchColumnPosts(id)
|
||||
])
|
||||
column.value = colData
|
||||
posts.value = postsData
|
||||
|
||||
// 初始化动画观察者
|
||||
await nextTick()
|
||||
initObserver()
|
||||
|
||||
// 刷新图标
|
||||
if ((window as any).lucide) {
|
||||
(window as any).lucide.createIcons()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch column:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
// 确保在加载完成后也初始化动画
|
||||
await nextTick()
|
||||
initObserver()
|
||||
if ((window as any).lucide) {
|
||||
(window as any).lucide.createIcons()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -48,23 +48,32 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { fetchColumns, Column } from '../services/api'
|
||||
import { useScrollAnimation } from '../composables/useScrollAnimation'
|
||||
|
||||
const router = useRouter()
|
||||
const columns = ref<Column[]>([])
|
||||
const loading = ref(true)
|
||||
const { initObserver } = useScrollAnimation()
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await fetchColumns()
|
||||
// Filter active columns
|
||||
columns.value = data.filter(c => c.isActive === 1)
|
||||
|
||||
// 初始化动画观察者
|
||||
await nextTick()
|
||||
initObserver()
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch columns:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
// 确保在加载完成后也初始化动画
|
||||
await nextTick()
|
||||
initObserver()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<div class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=2564&auto=format&fit=crop')] bg-cover bg-center transition-transform duration-700 group-hover:scale-105 opacity-60 mix-blend-overlay"></div>
|
||||
<div class="relative z-20 space-y-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="px-2 py-1 text-[10px] font-mono border border-white/20 rounded-full text-white backdrop-blur-sm">{{ featuredPost.category }}</span>
|
||||
<span class="px-2 py-1 text-[10px] font-mono border border-white/20 rounded-full text-white backdrop-blur-sm">{{ featuredPost.categoryName || '未分类' }}</span>
|
||||
<span class="text-xs text-white/60">{{ featuredPost.date }}</span>
|
||||
</div>
|
||||
<h3 class="font-serif text-3xl text-white group-hover:text-art-accent transition-colors">当极简主义遇见复杂数据:Dashboard 设计哲学</h3>
|
||||
|
||||
304
client/src/pages/admin/Attachments.vue
Normal file
304
client/src/pages/admin/Attachments.vue
Normal file
@@ -0,0 +1,304 @@
|
||||
<template>
|
||||
<div class="w-full animate-reveal">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-serif italic text-white">附件管理</h1>
|
||||
<button @click="showUploadModal = true" class="admin-btn-primary">
|
||||
上传附件
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="admin-card p-4 mb-6 flex gap-4 items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-art-muted">分类:</label>
|
||||
<div class="w-48">
|
||||
<CustomSelect
|
||||
v-model="filterCategoryId"
|
||||
:options="categoryOptions"
|
||||
placeholder="全部"
|
||||
@update:modelValue="loadAttachments"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-art-muted">类型:</label>
|
||||
<div class="w-48">
|
||||
<CustomSelect
|
||||
v-model="filterFileType"
|
||||
:options="fileTypeOptions"
|
||||
placeholder="全部"
|
||||
@update:modelValue="loadAttachments"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attachments Grid -->
|
||||
<div v-if="loading" class="flex justify-center items-center h-64">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-art-accent"></div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="attachments.length === 0" class="text-center py-20 text-art-muted">
|
||||
<p>暂无附件</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
||||
<div
|
||||
v-for="attachment in attachments"
|
||||
:key="attachment.id"
|
||||
class="admin-card p-4 group cursor-pointer hover:border-art-accent transition-colors"
|
||||
>
|
||||
<div v-if="attachment.fileType === 'image'" class="aspect-square mb-2 rounded overflow-hidden bg-white/5">
|
||||
<img :src="attachment.fileUrl" :alt="attachment.originalName" class="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div v-else class="aspect-square mb-2 rounded bg-white/5 flex items-center justify-center">
|
||||
<i data-lucide="file" class="w-12 h-12 text-art-muted"></i>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-white truncate mb-1" :title="attachment.originalName">
|
||||
{{ attachment.originalName }}
|
||||
</p>
|
||||
<p class="text-xs text-art-muted mb-2">
|
||||
{{ formatFileSize(attachment.fileSize) }}
|
||||
</p>
|
||||
|
||||
<div class="flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
@click.stop="copyUrl(attachment.fileUrl)"
|
||||
class="flex-1 px-2 py-1 text-xs bg-white/5 hover:bg-white/10 rounded transition-colors"
|
||||
>
|
||||
复制链接
|
||||
</button>
|
||||
<button
|
||||
@click.stop="deleteAttachment(attachment.id)"
|
||||
class="px-2 py-1 text-xs bg-red-500/20 hover:bg-red-500/30 text-red-400 rounded transition-colors"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Modal -->
|
||||
<div v-if="showUploadModal" class="fixed inset-0 bg-black/50 flex items-center justify-center z-50" @click.self="showUploadModal = false">
|
||||
<div class="admin-card max-w-md w-full mx-4">
|
||||
<h2 class="text-xl font-serif italic text-white mb-4">上传附件</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm text-art-muted mb-2">选择文件</label>
|
||||
<input
|
||||
type="file"
|
||||
ref="uploadInput"
|
||||
@change="handleUploadFile"
|
||||
class="admin-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm text-art-muted mb-2">分类(可选)</label>
|
||||
<CustomSelect
|
||||
v-model="uploadCategoryId"
|
||||
:options="uploadCategoryOptions"
|
||||
placeholder="无分类"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button @click="showUploadModal = false" class="admin-btn-secondary flex-1">
|
||||
取消
|
||||
</button>
|
||||
<button @click="uploadFile" :disabled="uploading" class="admin-btn-primary flex-1">
|
||||
{{ uploading ? '上传中...' : '上传' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useToast } from '../../composables/useToast'
|
||||
import CustomSelect from '../../components/CustomSelect.vue'
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
interface Attachment {
|
||||
id: number
|
||||
originalName: string
|
||||
fileUrl: string
|
||||
fileSize: number
|
||||
fileType: string
|
||||
categoryId?: number
|
||||
}
|
||||
|
||||
interface Category {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
const attachments = ref<Attachment[]>([])
|
||||
const categories = ref<Category[]>([])
|
||||
const loading = ref(false)
|
||||
const showUploadModal = ref(false)
|
||||
const uploadInput = ref<HTMLInputElement | null>(null)
|
||||
const uploadCategoryId = ref(0)
|
||||
const uploading = ref(false)
|
||||
const filterCategoryId = ref(0)
|
||||
const filterFileType = ref('')
|
||||
|
||||
// 计算属性:分类选项
|
||||
const categoryOptions = computed(() => {
|
||||
return [
|
||||
{ value: 0, label: '全部' },
|
||||
...categories.value.map(cat => ({ value: cat.id, label: cat.name }))
|
||||
]
|
||||
})
|
||||
|
||||
// 计算属性:文件类型选项
|
||||
const fileTypeOptions = computed(() => {
|
||||
return [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: 'image', label: '图片' },
|
||||
{ value: 'video', label: '视频' },
|
||||
{ value: 'document', label: '文档' },
|
||||
{ value: 'other', label: '其他' }
|
||||
]
|
||||
})
|
||||
|
||||
// 计算属性:上传分类选项
|
||||
const uploadCategoryOptions = computed(() => {
|
||||
return [
|
||||
{ value: 0, label: '无分类' },
|
||||
...categories.value.map(cat => ({ value: cat.id, label: cat.name }))
|
||||
]
|
||||
})
|
||||
|
||||
const API_BASE = 'http://localhost:8081/api'
|
||||
const getAuthHeaders = () => {
|
||||
const token = localStorage.getItem('token')
|
||||
return {
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {})
|
||||
}
|
||||
}
|
||||
|
||||
const loadAttachments = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
let url = `${API_BASE}/admin/attachments?pageSize=100`
|
||||
if (filterCategoryId.value > 0) {
|
||||
url += `&categoryId=${filterCategoryId.value}`
|
||||
}
|
||||
if (filterFileType.value) {
|
||||
url += `&fileType=${filterFileType.value}`
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: getAuthHeaders()
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error('获取附件列表失败')
|
||||
|
||||
const data = await response.json()
|
||||
attachments.value = data.result.list || []
|
||||
} catch (err: any) {
|
||||
toast.showToast(err.message || '加载失败', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadCategories = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/admin/attachment-categories`, {
|
||||
headers: getAuthHeaders()
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error('获取分类失败')
|
||||
|
||||
const data = await response.json()
|
||||
categories.value = data.result || []
|
||||
} catch (err) {
|
||||
console.error('Failed to load categories:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleUploadFile = () => {
|
||||
// File selection handled in uploadFile
|
||||
}
|
||||
|
||||
const uploadFile = async () => {
|
||||
if (!uploadInput.value?.files || uploadInput.value.files.length === 0) {
|
||||
toast.showToast('请选择文件', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
uploading.value = true
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', uploadInput.value.files[0])
|
||||
if (uploadCategoryId.value > 0) {
|
||||
formData.append('categoryId', uploadCategoryId.value.toString())
|
||||
}
|
||||
formData.append('storageType', 'local')
|
||||
|
||||
const response = await fetch(`${API_BASE}/admin/attachments/upload`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: formData
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.message || '上传失败')
|
||||
}
|
||||
|
||||
toast.showToast('上传成功', 'success')
|
||||
showUploadModal.value = false
|
||||
uploadInput.value.value = ''
|
||||
uploadCategoryId.value = 0
|
||||
loadAttachments()
|
||||
} catch (err: any) {
|
||||
toast.showToast(err.message || '上传失败', 'error')
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAttachment = async (id: number) => {
|
||||
if (!confirm('确定要删除这个附件吗?')) return
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/admin/attachments/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: getAuthHeaders()
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error('删除失败')
|
||||
|
||||
toast.showToast('删除成功', 'success')
|
||||
loadAttachments()
|
||||
} catch (err: any) {
|
||||
toast.showToast(err.message || '删除失败', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const copyUrl = (url: string) => {
|
||||
navigator.clipboard.writeText(url)
|
||||
toast.showToast('链接已复制', 'success')
|
||||
}
|
||||
|
||||
const formatFileSize = (bytes: number): string => {
|
||||
if (bytes < 1024) return bytes + ' B'
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadCategories()
|
||||
loadAttachments()
|
||||
})
|
||||
</script>
|
||||
@@ -95,6 +95,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Column Field -->
|
||||
<div class="space-y-2">
|
||||
<label for="column" class="block text-xs font-medium text-art-muted uppercase tracking-wider">专栏 <span class="text-art-muted text-xs">(可选)</span></label>
|
||||
<CustomSelect
|
||||
v-model="form.columnId"
|
||||
:options="columns"
|
||||
placeholder="选择专栏(可选)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tags Field -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-art-muted uppercase tracking-wider">标签</label>
|
||||
@@ -157,7 +167,7 @@ import { MdEditor } from 'md-editor-v3'
|
||||
import 'md-editor-v3/lib/style.css'
|
||||
|
||||
import { useToast } from '../../composables/useToast'
|
||||
import { createPost, updatePost, fetchPost, fetchCategories, fetchTags, Category, Tag } from '../../services/api'
|
||||
import { createPost, updatePost, fetchPost, fetchCategories, fetchColumns, fetchTags, Category, Tag } from '../../services/api'
|
||||
import CustomSelect from '../../components/CustomSelect.vue'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -177,12 +187,14 @@ const errors = reactive<Record<string, string>>({})
|
||||
|
||||
// Data sources
|
||||
const categories = ref<{value: number, label: string}[]>([])
|
||||
const columns = ref<{value: number, label: string}[]>([])
|
||||
const availableTags = ref<Tag[]>([])
|
||||
|
||||
// Form data
|
||||
const form = reactive({
|
||||
title: '',
|
||||
categoryId: 0,
|
||||
columnId: 0,
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
excerpt: '',
|
||||
content: '',
|
||||
@@ -254,7 +266,7 @@ const handleSubmit = async () => {
|
||||
|
||||
try {
|
||||
// Construct payload
|
||||
const payload = {
|
||||
const payload: any = {
|
||||
title: form.title,
|
||||
categoryId: form.categoryId,
|
||||
date: form.date,
|
||||
@@ -264,6 +276,13 @@ const handleSubmit = async () => {
|
||||
tags: form.tagIds.map(id => ({ id } as any)) // Backend expects objects with ID
|
||||
}
|
||||
|
||||
// Add columnId if selected
|
||||
if (form.columnId > 0) {
|
||||
payload.columnId = form.columnId
|
||||
} else {
|
||||
payload.columnId = null
|
||||
}
|
||||
|
||||
if (isEditing.value) {
|
||||
// Update existing post
|
||||
await updatePost(route.params.id as string, payload)
|
||||
@@ -292,9 +311,10 @@ const handleCancel = () => {
|
||||
// Load initial data
|
||||
const loadData = async () => {
|
||||
try {
|
||||
// Fetch categories and tags in parallel
|
||||
const [cats, tags] = await Promise.all([
|
||||
// Fetch categories, columns and tags in parallel
|
||||
const [cats, cols, tagList] = await Promise.all([
|
||||
fetchCategories(),
|
||||
fetchColumns(),
|
||||
fetchTags()
|
||||
])
|
||||
|
||||
@@ -303,7 +323,12 @@ const loadData = async () => {
|
||||
label: c.name
|
||||
}))
|
||||
|
||||
availableTags.value = tags
|
||||
columns.value = cols.map(c => ({
|
||||
value: c.id,
|
||||
label: c.name
|
||||
}))
|
||||
|
||||
availableTags.value = tagList
|
||||
|
||||
// If editing, load post data
|
||||
if (isEditing.value) {
|
||||
@@ -313,6 +338,7 @@ const loadData = async () => {
|
||||
// Populate form with post data
|
||||
form.title = post.title
|
||||
form.categoryId = post.categoryId
|
||||
form.columnId = post.columnId || 0
|
||||
form.date = post.date
|
||||
form.excerpt = post.excerpt || ''
|
||||
form.content = post.content || ''
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
<th class="w-20">ID</th>
|
||||
<th>标题</th>
|
||||
<th>分类</th>
|
||||
<th>专栏</th>
|
||||
<th>标签</th>
|
||||
<th>发布日期</th>
|
||||
<th>状态</th>
|
||||
<th class="text-right">操作</th>
|
||||
@@ -26,6 +28,25 @@
|
||||
<td class="font-mono text-xs text-white/40">#{{ post.id }}</td>
|
||||
<td class="font-medium text-white group-hover:text-art-accent transition-colors">{{ post.title }}</td>
|
||||
<td><span class="px-2 py-1 rounded bg-white/5 border border-white/5 text-xs text-art-muted">{{ post.categoryName || post.category?.name || '未分类' }}</span></td>
|
||||
<td>
|
||||
<span v-if="post.columnName || post.column?.name" class="px-2 py-1 rounded bg-art-accent/10 border border-art-accent/20 text-xs text-art-accent">
|
||||
{{ post.columnName || post.column?.name }}
|
||||
</span>
|
||||
<span v-else class="text-art-muted text-xs">-</span>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="post.tags && post.tags.length > 0" class="flex flex-wrap gap-1">
|
||||
<span
|
||||
v-for="tag in post.tags.slice(0, 3)"
|
||||
:key="tag.id"
|
||||
class="px-2 py-0.5 rounded bg-white/5 border border-white/5 text-xs text-art-muted"
|
||||
>
|
||||
#{{ tag.name }}
|
||||
</span>
|
||||
<span v-if="post.tags.length > 3" class="text-art-muted text-xs">+{{ post.tags.length - 3 }}</span>
|
||||
</div>
|
||||
<span v-else class="text-art-muted text-xs">-</span>
|
||||
</td>
|
||||
<td class="text-art-muted text-xs">{{ post.date }}</td>
|
||||
<td>
|
||||
<button
|
||||
@@ -38,6 +59,9 @@
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
||||
<button @click="openRelationModal(post)" class="admin-btn-secondary py-1 px-3 text-xs">
|
||||
管理
|
||||
</button>
|
||||
<router-link :to="`/admin/posts/${post.id}/edit`" class="admin-btn-secondary py-1 px-3 text-xs">
|
||||
编辑
|
||||
</router-link>
|
||||
@@ -61,33 +85,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category Modal -->
|
||||
<div v-if="isCategoryModalOpen" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm" @click.self="closeCategoryModal">
|
||||
<div class="bg-[#1a1a1a] border border-white/10 rounded-lg shadow-xl w-full max-w-md overflow-hidden animate-reveal">
|
||||
<div class="p-4 border-b border-white/10 flex justify-between items-center">
|
||||
<h3 class="text-lg font-serif italic text-white">修改文章分类</h3>
|
||||
<button @click="closeCategoryModal" class="text-white/50 hover:text-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-6 space-y-4">
|
||||
<p class="text-sm text-art-muted">正在修改文章: <span class="text-white">{{ editingPost?.title }}</span></p>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-art-muted uppercase tracking-wider">选择分类</label>
|
||||
<CustomSelect
|
||||
v-model="selectedCategoryId"
|
||||
:options="categories"
|
||||
placeholder="请选择分类"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 border-t border-white/10 flex justify-end gap-3 bg-white/5">
|
||||
<button @click="closeCategoryModal" class="px-4 py-2 text-sm text-white/70 hover:text-white transition-colors">取消</button>
|
||||
<button @click="saveCategory" class="admin-btn-primary py-1.5 px-4 text-sm">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Post Relation Modal -->
|
||||
<PostRelationModal
|
||||
v-model:isOpen="isRelationModalOpen"
|
||||
:post="editingPost"
|
||||
@saved="fetchPosts"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -96,10 +99,18 @@ import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getAdminPosts, deletePost as deletePostApi, Post, togglePostStatus } from '../../services/api'
|
||||
import { useToast } from '../../composables/useToast'
|
||||
import PostRelationModal from '../../components/admin/PostRelationModal.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const posts = ref<Post[]>([])
|
||||
const isRelationModalOpen = ref(false)
|
||||
const editingPost = ref<Post | null>(null)
|
||||
|
||||
const openRelationModal = (post: Post) => {
|
||||
editingPost.value = post
|
||||
isRelationModalOpen.value = true
|
||||
}
|
||||
|
||||
const fetchPosts = async () => {
|
||||
try {
|
||||
|
||||
@@ -2,9 +2,38 @@
|
||||
<div class="w-full animate-reveal">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-serif italic text-white">系统配置管理</h1>
|
||||
<!-- Optional: Add Create Button if needed -->
|
||||
<!-- <button @click="openCreateModal" class="admin-btn-primary">+ 新增配置</button> -->
|
||||
</div>
|
||||
|
||||
<!-- Tab切换 -->
|
||||
<div class="mb-6 border-b border-white/10">
|
||||
<div class="flex gap-4">
|
||||
<button
|
||||
@click="activeTab = 'settings'"
|
||||
:class="[
|
||||
'px-4 py-2 text-sm font-medium transition-colors border-b-2',
|
||||
activeTab === 'settings'
|
||||
? 'text-art-accent border-art-accent'
|
||||
: 'text-art-muted border-transparent hover:text-white'
|
||||
]"
|
||||
>
|
||||
基础配置
|
||||
</button>
|
||||
<button
|
||||
@click="activeTab = 'oss'"
|
||||
:class="[
|
||||
'px-4 py-2 text-sm font-medium transition-colors border-b-2',
|
||||
activeTab === 'oss'
|
||||
? 'text-art-accent border-art-accent'
|
||||
: 'text-art-muted border-transparent hover:text-white'
|
||||
]"
|
||||
>
|
||||
OSS配置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 基础配置Tab -->
|
||||
<div v-if="activeTab === 'settings'">
|
||||
|
||||
<div class="admin-card overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
@@ -101,19 +130,241 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OSS配置Tab -->
|
||||
<div v-if="activeTab === 'oss'">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-medium text-white">OSS存储配置</h2>
|
||||
<button @click="openOSSModal" class="admin-btn-primary">
|
||||
+ 新增OSS配置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="admin-card overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-1/6">名称</th>
|
||||
<th class="w-1/6">存储类型</th>
|
||||
<th class="w-1/6">Bucket</th>
|
||||
<th class="w-1/6">区域</th>
|
||||
<th class="w-1/6">域名</th>
|
||||
<th class="w-1/12">状态</th>
|
||||
<th class="text-right w-32">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="config in ossConfigs" :key="config.id" class="group transition-colors duration-200">
|
||||
<td class="font-medium text-white">{{ config.name }}</td>
|
||||
<td class="text-art-muted">
|
||||
<span class="px-2 py-1 text-xs rounded bg-white/5">
|
||||
{{ getStorageTypeLabel(config.storageType) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-art-muted text-sm">{{ config.bucket || '-' }}</td>
|
||||
<td class="text-art-muted text-sm">{{ config.region || '-' }}</td>
|
||||
<td class="text-art-muted text-sm max-w-xs truncate" :title="config.domain">{{ config.domain || '-' }}</td>
|
||||
<td>
|
||||
<span
|
||||
:class="[
|
||||
'px-2 py-1 text-xs rounded',
|
||||
config.isActive === 1
|
||||
? 'bg-green-500/20 text-green-400'
|
||||
: 'bg-white/5 text-art-muted'
|
||||
]"
|
||||
>
|
||||
{{ config.isActive === 1 ? '已启用' : '已停用' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
||||
<button @click="editOSSConfig(config)" class="admin-btn-secondary py-1 px-3 text-xs">
|
||||
编辑
|
||||
</button>
|
||||
<button @click="deleteOSSConfig(config.id)" class="admin-btn-danger py-1 px-3 text-xs">
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="ossConfigs.length === 0" class="p-16 text-center">
|
||||
<div class="w-16 h-16 bg-white/5 rounded-full flex items-center justify-center mx-auto mb-4 text-2xl">☁️</div>
|
||||
<h3 class="text-white font-medium mb-2">暂无OSS配置</h3>
|
||||
<p class="text-art-muted text-sm mb-4">点击上方按钮添加OSS配置</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OSS配置编辑Modal -->
|
||||
<div v-if="showOSSModal" class="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div class="absolute inset-0 bg-black/80 backdrop-blur-sm transition-opacity" @click="closeOSSModal"></div>
|
||||
|
||||
<div class="admin-card w-full max-w-2xl relative z-10 flex flex-col max-h-[90vh] shadow-2xl animate-reveal">
|
||||
<div class="flex items-center justify-between p-6 border-b border-white/5">
|
||||
<h2 class="text-lg font-medium text-white">{{ editingOSSConfig ? '编辑OSS配置' : '新增OSS配置' }}</h2>
|
||||
<button @click="closeOSSModal" class="text-white/40 hover:text-white transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6 overflow-y-auto">
|
||||
<form @submit.prevent="saveOSSConfig" class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<label for="ossName" class="block text-xs font-medium text-art-muted uppercase tracking-wider">配置名称</label>
|
||||
<input
|
||||
type="text"
|
||||
id="ossName"
|
||||
v-model="ossForm.name"
|
||||
required
|
||||
class="admin-input"
|
||||
placeholder="例如: 腾讯云CDN"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label for="storageType" class="block text-xs font-medium text-art-muted uppercase tracking-wider">存储类型</label>
|
||||
<CustomSelect
|
||||
v-model="ossForm.storageType"
|
||||
:options="storageTypeOptions"
|
||||
placeholder="选择存储类型"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label for="accessKey" class="block text-xs font-medium text-art-muted uppercase tracking-wider">Access Key</label>
|
||||
<input
|
||||
type="text"
|
||||
id="accessKey"
|
||||
v-model="ossForm.accessKey"
|
||||
required
|
||||
class="admin-input font-mono"
|
||||
placeholder="访问密钥ID"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label for="secretKey" class="block text-xs font-medium text-art-muted uppercase tracking-wider">Secret Key</label>
|
||||
<input
|
||||
type="password"
|
||||
id="secretKey"
|
||||
v-model="ossForm.secretKey"
|
||||
required
|
||||
class="admin-input font-mono"
|
||||
placeholder="访问密钥Secret"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<label for="bucket" class="block text-xs font-medium text-art-muted uppercase tracking-wider">Bucket</label>
|
||||
<input
|
||||
type="text"
|
||||
id="bucket"
|
||||
v-model="ossForm.bucket"
|
||||
class="admin-input"
|
||||
placeholder="存储桶名称"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label for="region" class="block text-xs font-medium text-art-muted uppercase tracking-wider">区域</label>
|
||||
<input
|
||||
type="text"
|
||||
id="region"
|
||||
v-model="ossForm.region"
|
||||
class="admin-input"
|
||||
placeholder="例如: ap-beijing"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label for="domain" class="block text-xs font-medium text-art-muted uppercase tracking-wider">访问域名</label>
|
||||
<input
|
||||
type="text"
|
||||
id="domain"
|
||||
v-model="ossForm.domain"
|
||||
class="admin-input"
|
||||
placeholder="例如: https://cdn.example.com"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="ossForm.isActive"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
class="w-4 h-4 rounded border-white/20 bg-white/5 text-art-accent focus:ring-art-accent"
|
||||
>
|
||||
<span class="text-sm text-art-muted">启用此配置</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="pt-4 flex justify-end gap-3">
|
||||
<button type="button" @click="closeOSSModal" class="admin-btn-secondary">取消</button>
|
||||
<button type="submit" class="admin-btn-primary">保存</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { getSettings, createSetting, updateSetting, deleteSetting as deleteSettingApi, Setting } from '../../services/api'
|
||||
import { getOSSConfigs, createOSSConfig, updateOSSConfig, deleteOSSConfig as deleteOSSConfigApi, OSSConfig } from '../../services/api'
|
||||
import { useToast } from '../../composables/useToast'
|
||||
import CustomSelect from '../../components/CustomSelect.vue'
|
||||
|
||||
const toast = useToast()
|
||||
const settings = ref<Setting[]>([])
|
||||
const showModal = ref(false)
|
||||
const editingSetting = ref(false)
|
||||
|
||||
// Tab管理
|
||||
const activeTab = ref<'settings' | 'oss'>('settings')
|
||||
|
||||
// OSS配置管理
|
||||
const ossConfigs = ref<OSSConfig[]>([])
|
||||
const showOSSModal = ref(false)
|
||||
const editingOSSConfig = ref(false)
|
||||
|
||||
const ossForm = ref({
|
||||
id: 0,
|
||||
name: '',
|
||||
storageType: '',
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
bucket: '',
|
||||
region: '',
|
||||
domain: '',
|
||||
isActive: 0 as number
|
||||
})
|
||||
|
||||
// 存储类型选项
|
||||
const storageTypeOptions = [
|
||||
{ value: 'local', label: '本地存储' },
|
||||
{ value: 'qcloud', label: '腾讯云COS' },
|
||||
{ value: 'aliyun', label: '阿里云OSS' },
|
||||
{ value: 'qiniu', label: '七牛云' }
|
||||
]
|
||||
|
||||
// 获取存储类型标签
|
||||
const getStorageTypeLabel = (type: string): string => {
|
||||
const option = storageTypeOptions.find(opt => opt.value === type)
|
||||
return option ? option.label : type
|
||||
}
|
||||
|
||||
const form = ref({
|
||||
id: 0,
|
||||
keyName: '',
|
||||
@@ -186,8 +437,126 @@ const closeModal = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// OSS配置相关函数
|
||||
const fetchOSSConfigs = async () => {
|
||||
try {
|
||||
ossConfigs.value = await getOSSConfigs()
|
||||
} catch (error) {
|
||||
console.error('Error fetching OSS configs:', error)
|
||||
toast.showToast('获取OSS配置失败', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const openOSSModal = () => {
|
||||
editingOSSConfig.value = false
|
||||
ossForm.value = {
|
||||
id: 0,
|
||||
name: '',
|
||||
storageType: '',
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
bucket: '',
|
||||
region: '',
|
||||
domain: '',
|
||||
isActive: 0
|
||||
}
|
||||
showOSSModal.value = true
|
||||
}
|
||||
|
||||
const editOSSConfig = (config: OSSConfig) => {
|
||||
editingOSSConfig.value = true
|
||||
ossForm.value = {
|
||||
id: config.id,
|
||||
name: config.name,
|
||||
storageType: config.storageType,
|
||||
accessKey: config.accessKey === '***' ? '' : config.accessKey, // 如果是加密值,清空让用户重新输入
|
||||
secretKey: config.secretKey === '***' ? '' : config.secretKey,
|
||||
bucket: config.bucket,
|
||||
region: config.region,
|
||||
domain: config.domain,
|
||||
isActive: config.isActive
|
||||
}
|
||||
showOSSModal.value = true
|
||||
}
|
||||
|
||||
const saveOSSConfig = async () => {
|
||||
try {
|
||||
if (editingOSSConfig.value) {
|
||||
// 更新时,如果密钥是空的,不发送密钥字段
|
||||
const updateData: any = {
|
||||
name: ossForm.value.name,
|
||||
storageType: ossForm.value.storageType,
|
||||
bucket: ossForm.value.bucket,
|
||||
region: ossForm.value.region,
|
||||
domain: ossForm.value.domain,
|
||||
isActive: ossForm.value.isActive
|
||||
}
|
||||
|
||||
// 只有用户输入了新密钥时才更新
|
||||
if (ossForm.value.accessKey && ossForm.value.accessKey !== '***') {
|
||||
updateData.accessKey = ossForm.value.accessKey
|
||||
}
|
||||
if (ossForm.value.secretKey && ossForm.value.secretKey !== '***') {
|
||||
updateData.secretKey = ossForm.value.secretKey
|
||||
}
|
||||
|
||||
await updateOSSConfig(ossForm.value.id, updateData)
|
||||
toast.showToast('OSS配置更新成功', 'success')
|
||||
} else {
|
||||
await createOSSConfig({
|
||||
name: ossForm.value.name,
|
||||
storageType: ossForm.value.storageType,
|
||||
accessKey: ossForm.value.accessKey,
|
||||
secretKey: ossForm.value.secretKey,
|
||||
bucket: ossForm.value.bucket,
|
||||
region: ossForm.value.region,
|
||||
domain: ossForm.value.domain,
|
||||
isActive: ossForm.value.isActive,
|
||||
createdAt: '',
|
||||
updatedAt: ''
|
||||
})
|
||||
toast.showToast('OSS配置创建成功', 'success')
|
||||
}
|
||||
closeOSSModal()
|
||||
fetchOSSConfigs()
|
||||
} catch (error) {
|
||||
console.error('Error saving OSS config:', error)
|
||||
toast.showToast(editingOSSConfig.value ? '更新OSS配置失败' : '创建OSS配置失败', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const deleteOSSConfig = async (id: number) => {
|
||||
if (confirm(`确定要删除这个OSS配置吗?`)) {
|
||||
try {
|
||||
await deleteOSSConfigApi(id)
|
||||
toast.showToast('OSS配置删除成功', 'success')
|
||||
fetchOSSConfigs()
|
||||
} catch (error) {
|
||||
console.error('Error deleting OSS config:', error)
|
||||
toast.showToast('删除OSS配置失败', 'error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const closeOSSModal = () => {
|
||||
showOSSModal.value = false
|
||||
editingOSSConfig.value = false
|
||||
ossForm.value = {
|
||||
id: 0,
|
||||
name: '',
|
||||
storageType: '',
|
||||
accessKey: '',
|
||||
secretKey: '',
|
||||
bucket: '',
|
||||
region: '',
|
||||
domain: '',
|
||||
isActive: 0
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchSettings()
|
||||
fetchOSSConfigs()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ const routes = [
|
||||
{ path: '/', name: 'home', component: () => import('./pages/Home.vue') },
|
||||
{ path: '/blog', name: 'blog', component: () => import('./pages/Blog.vue') },
|
||||
{ path: '/blog/:id', name: 'blog-detail', component: () => import('./pages/BlogDetail.vue') },
|
||||
{ path: '/columns', name: 'columns', component: () => import('./pages/Columns.vue') },
|
||||
{ path: '/columns/:id', name: 'column-detail', component: () => import('./pages/ColumnDetail.vue') },
|
||||
{ path: '/works', name: 'works', component: () => import('./pages/Works.vue') },
|
||||
{ path: '/works/:id', name: 'work-detail', component: () => import('./pages/WorkDetail.vue') },
|
||||
{ path: '/snippets', name: 'snippets', component: () => import('./pages/Snippets.vue') },
|
||||
@@ -89,7 +91,10 @@ const routes = [
|
||||
{ path: 'settings', name: 'admin-settings', component: () => import('./pages/admin/Settings.vue') },
|
||||
|
||||
// 操作日志
|
||||
{ path: 'logs', name: 'admin-logs', component: () => import('./pages/admin/Logs.vue') }
|
||||
{ path: 'logs', name: 'admin-logs', component: () => import('./pages/admin/Logs.vue') },
|
||||
|
||||
// 附件管理
|
||||
{ path: 'attachments', name: 'admin-attachments', component: () => import('./pages/admin/Attachments.vue') }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -94,6 +94,8 @@ export interface Post {
|
||||
categoryName?: string // Display name
|
||||
categorySlug?: string
|
||||
category?: Category // Optional full object
|
||||
columnId?: number | null
|
||||
columnName?: string
|
||||
tags?: Tag[]
|
||||
date: string
|
||||
excerpt?: string
|
||||
@@ -515,13 +517,14 @@ export const getAdminPosts = async (): Promise<PaginationResponse<Post>> => {
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchPosts = async (query?: string, categoryId?: number, tagId?: number): Promise<Post[]> => {
|
||||
export const fetchPosts = async (query?: string, categoryId?: number, tagId?: number, columnId?: number): Promise<Post[]> => {
|
||||
try {
|
||||
let url = `${API_BASE}/posts`
|
||||
const params = new URLSearchParams()
|
||||
if (query) params.append('q', query)
|
||||
if (categoryId) params.append('category', categoryId.toString())
|
||||
if (tagId) params.append('tag', tagId.toString())
|
||||
if (columnId) params.append('column', columnId.toString())
|
||||
|
||||
if (params.toString()) {
|
||||
url += `?${params.toString()}`
|
||||
@@ -1440,6 +1443,21 @@ export interface Inquiry {
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
// OSS配置相关类型
|
||||
export interface OSSConfig {
|
||||
id: number
|
||||
name: string
|
||||
storageType: string // local/qcloud/aliyun/qiniu
|
||||
accessKey: string // 加密存储,返回时显示为 ***
|
||||
secretKey: string // 加密存储,返回时显示为 ***
|
||||
bucket: string
|
||||
region: string
|
||||
domain: string
|
||||
isActive: number
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface EmailSuffix {
|
||||
id: number
|
||||
suffix: string
|
||||
@@ -1500,3 +1518,75 @@ export const fetchInquiries = async (): Promise<Inquiry[]> => {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// OSS配置相关API
|
||||
export const getOSSConfigs = async (): Promise<OSSConfig[]> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/admin/oss-configs`, {
|
||||
headers: getAuthHeaders()
|
||||
})
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.message || '获取OSS配置失败')
|
||||
}
|
||||
const data = await response.json()
|
||||
return data.result || []
|
||||
} catch (error) {
|
||||
console.error('Get OSS configs error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export const createOSSConfig = async (configData: Omit<OSSConfig, 'id' | 'createdAt' | 'updatedAt'>): Promise<OSSConfig> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/admin/oss-configs`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify(configData)
|
||||
})
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.message || '创建OSS配置失败')
|
||||
}
|
||||
const data = await response.json()
|
||||
return data.result
|
||||
} catch (error) {
|
||||
console.error('Create OSS config error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export const updateOSSConfig = async (id: number, configData: Partial<Omit<OSSConfig, 'id' | 'createdAt' | 'updatedAt'>>): Promise<OSSConfig> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/admin/oss-configs/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify(configData)
|
||||
})
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.message || '更新OSS配置失败')
|
||||
}
|
||||
const data = await response.json()
|
||||
return data.result
|
||||
} catch (error) {
|
||||
console.error('Update OSS config error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteOSSConfig = async (id: number): Promise<void> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/admin/oss-configs/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: getAuthHeaders()
|
||||
})
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.message || '删除OSS配置失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Delete OSS config error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ export default defineConfig({
|
||||
'/api': {
|
||||
target: 'http://localhost:8081',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/uploads': {
|
||||
target: 'http://localhost:8081',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
server/cleanup_tables.sql
Normal file
15
server/cleanup_tables.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- 清理脚本:删除可能有问题的表
|
||||
-- 在执行 nl_blog.sql 之前先执行此脚本
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- 删除 operation_logs 表(如果存在)
|
||||
DROP TABLE IF EXISTS `operation_logs`;
|
||||
|
||||
-- 删除 tags 表(如果存在)
|
||||
DROP TABLE IF EXISTS `tags`;
|
||||
|
||||
-- 删除 post_tags 关联表(如果存在,因为它可能引用 tags)
|
||||
DROP TABLE IF EXISTS `post_tags`;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
var DB *gorm.DB
|
||||
|
||||
// JWTSecret is the secret key used for signing JWT tokens
|
||||
var JWTSecret = "your-secret-key-change-this-in-production" // Default value
|
||||
var JWTSecret = "your-secret-key" // Default value, should be set via JWT_SECRET environment variable in production
|
||||
|
||||
func InitDB() {
|
||||
var err error
|
||||
|
||||
447
server/handlers/attachment.go
Normal file
447
server/handlers/attachment.go
Normal file
@@ -0,0 +1,447 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"github.com/niangaodev/art-code/repositories"
|
||||
"github.com/niangaodev/art-code/utils"
|
||||
)
|
||||
|
||||
// AdminUploadAttachment 上传附件
|
||||
func AdminUploadAttachment(c *gin.Context) {
|
||||
// 获取文件
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
utils.Error(c, 400, "No file uploaded")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取分类ID(可选)
|
||||
categoryIDStr := c.PostForm("categoryId")
|
||||
var categoryID *uint
|
||||
if categoryIDStr != "" {
|
||||
if id, err := strconv.ParseUint(categoryIDStr, 10, 32); err == nil {
|
||||
idUint := uint(id)
|
||||
categoryID = &idUint
|
||||
}
|
||||
}
|
||||
|
||||
// 获取存储类型
|
||||
storageType := c.DefaultPostForm("storageType", "local")
|
||||
|
||||
// 打开文件
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to open file")
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// 获取OSS配置
|
||||
var ossConfig *models.OSSConfig
|
||||
if storageType != "local" {
|
||||
ossConfig, err = repositories.GetActiveOSSConfig(storageType)
|
||||
if err != nil || ossConfig == nil {
|
||||
utils.Error(c, 400, "OSS config not found or not active")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 创建OSS配置对象
|
||||
config := &utils.OSSConfig{
|
||||
StorageType: storageType,
|
||||
}
|
||||
|
||||
if ossConfig != nil {
|
||||
// 解密AccessKey和SecretKey
|
||||
accessKey, err := utils.DecryptAES(ossConfig.AccessKey)
|
||||
if err != nil {
|
||||
log.Printf("Failed to decrypt access key: %v", err)
|
||||
utils.Error(c, 500, "Failed to decrypt OSS credentials")
|
||||
return
|
||||
}
|
||||
|
||||
secretKey, err := utils.DecryptAES(ossConfig.SecretKey)
|
||||
if err != nil {
|
||||
log.Printf("Failed to decrypt secret key: %v", err)
|
||||
utils.Error(c, 500, "Failed to decrypt OSS credentials")
|
||||
return
|
||||
}
|
||||
|
||||
config.AccessKey = accessKey
|
||||
config.SecretKey = secretKey
|
||||
config.Bucket = ossConfig.Bucket
|
||||
config.Region = ossConfig.Region
|
||||
config.Domain = ossConfig.Domain
|
||||
}
|
||||
|
||||
// 获取上传器
|
||||
uploader, err := utils.GetOSSUploader(config)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
filePath, fileURL, err := uploader.Upload(src, file.Filename, file.Size)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to upload file: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 获取文件类型
|
||||
fileType := utils.GetFileType(file.Header.Get("Content-Type"))
|
||||
|
||||
// 创建附件记录
|
||||
attachment := &models.Attachment{
|
||||
CategoryID: categoryID,
|
||||
OriginalName: file.Filename,
|
||||
StoredName: file.Filename,
|
||||
FilePath: filePath,
|
||||
FileURL: fileURL,
|
||||
FileSize: file.Size,
|
||||
FileType: fileType,
|
||||
MimeType: file.Header.Get("Content-Type"),
|
||||
StorageType: storageType,
|
||||
}
|
||||
|
||||
if ossConfig != nil {
|
||||
ossConfigID := ossConfig.ID
|
||||
attachment.OSSConfigID = &ossConfigID
|
||||
}
|
||||
|
||||
if err := repositories.CreateAttachment(attachment); err != nil {
|
||||
// 如果数据库保存失败,尝试删除已上传的文件
|
||||
uploader.Delete(filePath)
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "File uploaded successfully", attachment)
|
||||
}
|
||||
|
||||
// AdminGetAttachments 获取附件列表
|
||||
func AdminGetAttachments(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
|
||||
categoryIDStr := c.Query("categoryId")
|
||||
fileType := c.Query("fileType")
|
||||
|
||||
var categoryID *uint
|
||||
if categoryIDStr != "" {
|
||||
if id, err := strconv.ParseUint(categoryIDStr, 10, 32); err == nil {
|
||||
idUint := uint(id)
|
||||
categoryID = &idUint
|
||||
}
|
||||
}
|
||||
|
||||
attachments, total, err := repositories.GetAttachments(page, pageSize, categoryID, fileType)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
res := gin.H{
|
||||
"list": attachments,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": pageSize,
|
||||
}
|
||||
|
||||
utils.Success(c, res)
|
||||
}
|
||||
|
||||
// AdminDeleteAttachment 删除附件
|
||||
func AdminDeleteAttachment(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
var id uint
|
||||
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
|
||||
utils.Error(c, 400, "Invalid attachment ID")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取附件信息
|
||||
attachment, err := repositories.GetAttachmentByID(id)
|
||||
if err != nil || attachment == nil {
|
||||
utils.Error(c, 404, "Attachment not found")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取OSS配置并删除文件
|
||||
if attachment.StorageType != "" {
|
||||
var ossConfig *models.OSSConfig
|
||||
if attachment.OSSConfigID != nil {
|
||||
ossConfig, err = repositories.GetOSSConfigByID(*attachment.OSSConfigID)
|
||||
} else {
|
||||
ossConfig, err = repositories.GetActiveOSSConfig(attachment.StorageType)
|
||||
}
|
||||
|
||||
if err == nil && ossConfig != nil {
|
||||
config := &utils.OSSConfig{
|
||||
StorageType: attachment.StorageType,
|
||||
}
|
||||
|
||||
// 解密密钥
|
||||
if accessKey, err := utils.DecryptAES(ossConfig.AccessKey); err == nil {
|
||||
config.AccessKey = accessKey
|
||||
}
|
||||
if secretKey, err := utils.DecryptAES(ossConfig.SecretKey); err == nil {
|
||||
config.SecretKey = secretKey
|
||||
}
|
||||
config.Bucket = ossConfig.Bucket
|
||||
config.Region = ossConfig.Region
|
||||
config.Domain = ossConfig.Domain
|
||||
|
||||
uploader, err := utils.GetOSSUploader(config)
|
||||
if err == nil {
|
||||
uploader.Delete(attachment.FilePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除数据库记录
|
||||
if err := repositories.DeleteAttachment(id); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Attachment deleted successfully", nil)
|
||||
}
|
||||
|
||||
// AdminGetAttachmentCategories 获取附件分类列表
|
||||
func AdminGetAttachmentCategories(c *gin.Context) {
|
||||
categories, err := repositories.GetAttachmentCategories()
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.Success(c, categories)
|
||||
}
|
||||
|
||||
// AdminCreateAttachmentCategory 创建附件分类
|
||||
func AdminCreateAttachmentCategory(c *gin.Context) {
|
||||
var category models.AttachmentCategory
|
||||
if err := c.ShouldBindJSON(&category); err != nil {
|
||||
utils.Error(c, 400, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
if err := repositories.CreateAttachmentCategory(&category); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Category created successfully", category)
|
||||
}
|
||||
|
||||
// AdminUpdateAttachmentCategory 更新附件分类
|
||||
func AdminUpdateAttachmentCategory(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
var id uint
|
||||
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
|
||||
utils.Error(c, 400, "Invalid category ID")
|
||||
return
|
||||
}
|
||||
|
||||
var category models.AttachmentCategory
|
||||
if err := c.ShouldBindJSON(&category); err != nil {
|
||||
utils.Error(c, 400, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
category.ID = id
|
||||
|
||||
if err := repositories.UpdateAttachmentCategory(&category); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Category updated successfully", nil)
|
||||
}
|
||||
|
||||
// AdminDeleteAttachmentCategory 删除附件分类
|
||||
func AdminDeleteAttachmentCategory(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
var id uint
|
||||
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
|
||||
utils.Error(c, 400, "Invalid category ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := repositories.DeleteAttachmentCategory(id); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Category deleted successfully", nil)
|
||||
}
|
||||
|
||||
// AdminGetOSSConfigs 获取OSS配置列表
|
||||
func AdminGetOSSConfigs(c *gin.Context) {
|
||||
configs, err := repositories.GetOSSConfigs()
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 不返回加密的密钥
|
||||
for i := range configs {
|
||||
configs[i].AccessKey = "***"
|
||||
configs[i].SecretKey = "***"
|
||||
}
|
||||
|
||||
utils.Success(c, configs)
|
||||
}
|
||||
|
||||
// AdminCreateOSSConfig 创建OSS配置
|
||||
func AdminCreateOSSConfig(c *gin.Context) {
|
||||
var req struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
StorageType string `json:"storageType" binding:"required"`
|
||||
AccessKey string `json:"accessKey" binding:"required"`
|
||||
SecretKey string `json:"secretKey" binding:"required"`
|
||||
Bucket string `json:"bucket"`
|
||||
Region string `json:"region"`
|
||||
Domain string `json:"domain"`
|
||||
IsActive int `json:"isActive"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.Error(c, 400, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
// 加密密钥
|
||||
encryptedAccessKey, err := utils.EncryptAES(req.AccessKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt access key")
|
||||
return
|
||||
}
|
||||
|
||||
encryptedSecretKey, err := utils.EncryptAES(req.SecretKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt secret key")
|
||||
return
|
||||
}
|
||||
|
||||
ossConfig := &models.OSSConfig{
|
||||
Name: req.Name,
|
||||
StorageType: req.StorageType,
|
||||
AccessKey: encryptedAccessKey,
|
||||
SecretKey: encryptedSecretKey,
|
||||
Bucket: req.Bucket,
|
||||
Region: req.Region,
|
||||
Domain: req.Domain,
|
||||
IsActive: req.IsActive,
|
||||
}
|
||||
|
||||
if err := repositories.CreateOSSConfig(ossConfig); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 不返回加密的密钥
|
||||
ossConfig.AccessKey = "***"
|
||||
ossConfig.SecretKey = "***"
|
||||
|
||||
utils.SuccessWithMsg(c, "OSS config created successfully", ossConfig)
|
||||
}
|
||||
|
||||
// AdminUpdateOSSConfig 更新OSS配置
|
||||
func AdminUpdateOSSConfig(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
var id uint
|
||||
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
|
||||
utils.Error(c, 400, "Invalid OSS config ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
StorageType string `json:"storageType"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Bucket string `json:"bucket"`
|
||||
Region string `json:"region"`
|
||||
Domain string `json:"domain"`
|
||||
IsActive int `json:"isActive"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.Error(c, 400, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取现有配置
|
||||
existingConfig, err := repositories.GetOSSConfigByID(id)
|
||||
if err != nil || existingConfig == nil {
|
||||
utils.Error(c, 404, "OSS config not found")
|
||||
return
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if req.Name != "" {
|
||||
existingConfig.Name = req.Name
|
||||
}
|
||||
if req.StorageType != "" {
|
||||
existingConfig.StorageType = req.StorageType
|
||||
}
|
||||
if req.AccessKey != "" && req.AccessKey != "***" {
|
||||
encryptedAccessKey, err := utils.EncryptAES(req.AccessKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt access key")
|
||||
return
|
||||
}
|
||||
existingConfig.AccessKey = encryptedAccessKey
|
||||
}
|
||||
if req.SecretKey != "" && req.SecretKey != "***" {
|
||||
encryptedSecretKey, err := utils.EncryptAES(req.SecretKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt secret key")
|
||||
return
|
||||
}
|
||||
existingConfig.SecretKey = encryptedSecretKey
|
||||
}
|
||||
if req.Bucket != "" {
|
||||
existingConfig.Bucket = req.Bucket
|
||||
}
|
||||
if req.Region != "" {
|
||||
existingConfig.Region = req.Region
|
||||
}
|
||||
if req.Domain != "" {
|
||||
existingConfig.Domain = req.Domain
|
||||
}
|
||||
existingConfig.IsActive = req.IsActive
|
||||
|
||||
if err := repositories.UpdateOSSConfig(existingConfig); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 不返回加密的密钥
|
||||
existingConfig.AccessKey = "***"
|
||||
existingConfig.SecretKey = "***"
|
||||
|
||||
utils.SuccessWithMsg(c, "OSS config updated successfully", existingConfig)
|
||||
}
|
||||
|
||||
// AdminDeleteOSSConfig 删除OSS配置
|
||||
func AdminDeleteOSSConfig(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
var id uint
|
||||
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
|
||||
utils.Error(c, 400, "Invalid OSS config ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := repositories.DeleteOSSConfig(id); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "OSS config deleted successfully", nil)
|
||||
}
|
||||
@@ -2,11 +2,9 @@ package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/middleware"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"github.com/niangaodev/art-code/repositories"
|
||||
"github.com/niangaodev/art-code/utils"
|
||||
@@ -38,18 +36,13 @@ func Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if !utils.CheckPasswordHash(req.Password, user.Password) {
|
||||
if !utils.CheckPasswordHash(req.Password, user.PasswordHash) {
|
||||
utils.Error(c, 401, "Invalid username or password")
|
||||
return
|
||||
}
|
||||
|
||||
// 生成JWT Token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"userID": user.ID,
|
||||
"exp": time.Now().Add(time.Hour * 24).Unix(), // 24小时过期
|
||||
})
|
||||
|
||||
tokenString, err := token.SignedString([]byte(config.JWTSecret))
|
||||
tokenString, expireUnix, err := middleware.GenerateToken(user.ID, user.Username, user.Role)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
@@ -70,7 +63,8 @@ func Login(c *gin.Context) {
|
||||
}()
|
||||
|
||||
utils.Success(c, gin.H{
|
||||
"token": tokenString,
|
||||
"token": tokenString,
|
||||
"expire": expireUnix,
|
||||
"user": gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
|
||||
@@ -38,7 +38,9 @@ func GetColumnByID(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Success(c, col)
|
||||
// 构建包含统计信息的响应
|
||||
response := repositories.BuildColumnResponse(col)
|
||||
utils.Success(c, response)
|
||||
}
|
||||
|
||||
// GetColumnPosts 获取专栏文章
|
||||
|
||||
@@ -17,6 +17,7 @@ func GetPosts(c *gin.Context) {
|
||||
keyword := c.Query("q")
|
||||
categoryIDStr := c.Query("category")
|
||||
tagIDStr := c.Query("tag")
|
||||
columnIDStr := c.Query("column")
|
||||
|
||||
var categoryID uint
|
||||
if categoryIDStr != "" {
|
||||
@@ -32,13 +33,37 @@ func GetPosts(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
var columnID uint
|
||||
if columnIDStr != "" {
|
||||
if id, err := strconv.ParseUint(columnIDStr, 10, 32); err == nil {
|
||||
columnID = uint(id)
|
||||
}
|
||||
}
|
||||
|
||||
// 从数据库获取所有博客文章
|
||||
posts, err := repositories.GetPosts(keyword, categoryID, tagID)
|
||||
posts, err := repositories.GetPosts(keyword, categoryID, tagID, columnID)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 异步记录搜索日志
|
||||
userIP := c.ClientIP()
|
||||
userLocation := utils.GetRegion(userIP)
|
||||
|
||||
if keyword != "" {
|
||||
LogSearch(keyword, "keyword", userIP, userLocation)
|
||||
}
|
||||
if categoryID > 0 {
|
||||
LogSearch(strconv.FormatUint(uint64(categoryID), 10), "category", userIP, userLocation)
|
||||
}
|
||||
if tagID > 0 {
|
||||
LogSearch(strconv.FormatUint(uint64(tagID), 10), "tag", userIP, userLocation)
|
||||
}
|
||||
if columnID > 0 {
|
||||
LogSearch(strconv.FormatUint(uint64(columnID), 10), "column", userIP, userLocation)
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
responses := repositories.BuildPostsResponse(posts)
|
||||
// Ensure not nil
|
||||
@@ -304,7 +329,7 @@ func GetPostsByTagID(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
posts, err := repositories.GetPosts("", 0, tagID)
|
||||
posts, err := repositories.GetPosts("", 0, tagID, 0)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
|
||||
65
server/handlers/search_log.go
Normal file
65
server/handlers/search_log.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"github.com/niangaodev/art-code/repositories"
|
||||
"github.com/niangaodev/art-code/utils"
|
||||
)
|
||||
|
||||
// AdminGetSearchLogs 获取搜索记录列表(后台)
|
||||
func AdminGetSearchLogs(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
|
||||
|
||||
logs, total, err := repositories.GetSearchLogs(page, pageSize)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
res := gin.H{
|
||||
"list": logs,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": pageSize,
|
||||
}
|
||||
|
||||
utils.Success(c, res)
|
||||
}
|
||||
|
||||
// AdminDeleteSearchLog 删除搜索记录
|
||||
func AdminDeleteSearchLog(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
var id uint
|
||||
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
|
||||
utils.Error(c, 400, "Invalid search log ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := repositories.DeleteSearchLog(id); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Search log deleted successfully", nil)
|
||||
}
|
||||
|
||||
// LogSearch 记录搜索(异步,不阻塞)
|
||||
func LogSearch(keyword, searchType, userIP, userLocation string) {
|
||||
go func() {
|
||||
logEntry := &models.SearchLog{
|
||||
Keyword: keyword,
|
||||
SearchType: searchType,
|
||||
UserIP: userIP,
|
||||
UserLocation: userLocation,
|
||||
}
|
||||
|
||||
if err := repositories.CreateSearchLog(logEntry); err != nil {
|
||||
// Log error but don't fail
|
||||
// log.Printf("Failed to create search log: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -30,6 +30,9 @@ func main() {
|
||||
router.Use(middleware.AccessLogMiddleware()) // 添加访问日志中间件
|
||||
router.Use(gin.Recovery())
|
||||
|
||||
// 静态文件服务 - 用于访问上传的本地文件
|
||||
router.Static("/uploads", "./uploads")
|
||||
|
||||
// API路由组
|
||||
api := router.Group("/api")
|
||||
{
|
||||
@@ -186,6 +189,27 @@ func main() {
|
||||
authAdmin.POST("/email-suffixes", middleware.PermissionMiddleware("settings", "create"), handlers.AdminCreateEmailSuffix)
|
||||
authAdmin.PUT("/email-suffixes/:id", middleware.PermissionMiddleware("settings", "update"), handlers.AdminUpdateEmailSuffix)
|
||||
authAdmin.DELETE("/email-suffixes/:id", middleware.PermissionMiddleware("settings", "delete"), handlers.AdminDeleteEmailSuffix)
|
||||
|
||||
// 搜索记录管理 (复用 settings 权限)
|
||||
authAdmin.GET("/search-logs", middleware.PermissionMiddleware("settings", "read"), handlers.AdminGetSearchLogs)
|
||||
authAdmin.DELETE("/search-logs/:id", middleware.PermissionMiddleware("settings", "delete"), handlers.AdminDeleteSearchLog)
|
||||
|
||||
// 附件管理 (复用 settings 权限)
|
||||
authAdmin.POST("/attachments/upload", middleware.PermissionMiddleware("settings", "create"), handlers.AdminUploadAttachment)
|
||||
authAdmin.GET("/attachments", middleware.PermissionMiddleware("settings", "read"), handlers.AdminGetAttachments)
|
||||
authAdmin.DELETE("/attachments/:id", middleware.PermissionMiddleware("settings", "delete"), handlers.AdminDeleteAttachment)
|
||||
|
||||
// 附件分类管理
|
||||
authAdmin.GET("/attachment-categories", middleware.PermissionMiddleware("settings", "read"), handlers.AdminGetAttachmentCategories)
|
||||
authAdmin.POST("/attachment-categories", middleware.PermissionMiddleware("settings", "create"), handlers.AdminCreateAttachmentCategory)
|
||||
authAdmin.PUT("/attachment-categories/:id", middleware.PermissionMiddleware("settings", "update"), handlers.AdminUpdateAttachmentCategory)
|
||||
authAdmin.DELETE("/attachment-categories/:id", middleware.PermissionMiddleware("settings", "delete"), handlers.AdminDeleteAttachmentCategory)
|
||||
|
||||
// OSS配置管理
|
||||
authAdmin.GET("/oss-configs", middleware.PermissionMiddleware("settings", "read"), handlers.AdminGetOSSConfigs)
|
||||
authAdmin.POST("/oss-configs", middleware.PermissionMiddleware("settings", "create"), handlers.AdminCreateOSSConfig)
|
||||
authAdmin.PUT("/oss-configs/:id", middleware.PermissionMiddleware("settings", "update"), handlers.AdminUpdateOSSConfig)
|
||||
authAdmin.DELETE("/oss-configs/:id", middleware.PermissionMiddleware("settings", "delete"), handlers.AdminDeleteOSSConfig)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
131
server/models/attachment.go
Normal file
131
server/models/attachment.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AttachmentCategory 附件分类模型
|
||||
type AttachmentCategory struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Description string `json:"description" gorm:"column:description;type:text"`
|
||||
SortOrder uint `json:"sortOrder" gorm:"column:sort_order;default:0"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (AttachmentCategory) TableName() string {
|
||||
return "attachment_categories"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (ac *AttachmentCategory) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if ac.CreatedAt == 0 {
|
||||
ac.CreatedAt = now
|
||||
}
|
||||
if ac.UpdatedAt == 0 {
|
||||
ac.UpdatedAt = now
|
||||
}
|
||||
if ac.DeletedAt == 0 {
|
||||
ac.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (ac *AttachmentCategory) BeforeUpdate(tx *gorm.DB) error {
|
||||
ac.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Attachment 附件模型
|
||||
type Attachment struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
CategoryID *uint `json:"categoryId,omitempty" gorm:"column:category_id"`
|
||||
Category *AttachmentCategory `json:"category,omitempty" gorm:"foreignKey:CategoryID"`
|
||||
OriginalName string `json:"originalName" gorm:"column:original_name"`
|
||||
StoredName string `json:"storedName" gorm:"column:stored_name"`
|
||||
FilePath string `json:"filePath" gorm:"column:file_path"`
|
||||
FileURL string `json:"fileUrl" gorm:"column:file_url"`
|
||||
FileSize int64 `json:"fileSize" gorm:"column:file_size"`
|
||||
FileType string `json:"fileType" gorm:"column:file_type"` // image/video/document/other
|
||||
MimeType string `json:"mimeType" gorm:"column:mime_type"`
|
||||
StorageType string `json:"storageType" gorm:"column:storage_type;default:local"` // local/qcloud/aliyun/qiniu
|
||||
OSSConfigID *uint `json:"ossConfigId,omitempty" gorm:"column:oss_config_id"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Attachment) TableName() string {
|
||||
return "attachments"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (a *Attachment) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if a.CreatedAt == 0 {
|
||||
a.CreatedAt = now
|
||||
}
|
||||
if a.UpdatedAt == 0 {
|
||||
a.UpdatedAt = now
|
||||
}
|
||||
if a.DeletedAt == 0 {
|
||||
a.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (a *Attachment) BeforeUpdate(tx *gorm.DB) error {
|
||||
a.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// OSSConfig OSS配置模型
|
||||
type OSSConfig struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
StorageType string `json:"storageType" gorm:"column:storage_type"` // local/qcloud/aliyun/qiniu
|
||||
AccessKey string `json:"accessKey" gorm:"column:access_key;type:text"` // AES加密存储
|
||||
SecretKey string `json:"secretKey" gorm:"column:secret_key;type:text"` // AES加密存储
|
||||
Bucket string `json:"bucket" gorm:"column:bucket"`
|
||||
Region string `json:"region" gorm:"column:region"`
|
||||
Domain string `json:"domain" gorm:"column:domain"`
|
||||
IsActive int `json:"isActive" gorm:"column:is_active;default:0"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (OSSConfig) TableName() string {
|
||||
return "oss_configs"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (oc *OSSConfig) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if oc.CreatedAt == 0 {
|
||||
oc.CreatedAt = now
|
||||
}
|
||||
if oc.UpdatedAt == 0 {
|
||||
oc.UpdatedAt = now
|
||||
}
|
||||
if oc.DeletedAt == 0 {
|
||||
oc.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前钩子
|
||||
func (oc *OSSConfig) BeforeUpdate(tx *gorm.DB) error {
|
||||
oc.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
@@ -12,7 +12,9 @@ type Post struct {
|
||||
OriginalID string `json:"originalId,omitempty" gorm:"-"` // For backward compatibility
|
||||
Title string `json:"title" gorm:"column:title"`
|
||||
CategoryID uint `json:"categoryId" gorm:"column:category_id"`
|
||||
Category *Category `json:"category,omitempty" gorm:"foreignKey:CategoryID"` // For join query result
|
||||
Category *Category `json:"category,omitempty" gorm:"foreignKey:CategoryID"` // For join query result
|
||||
ColumnID *uint `json:"columnId,omitempty" gorm:"column:column_id;default:NULL"` // Optional column association
|
||||
Column *Column `json:"column,omitempty" gorm:"foreignKey:ColumnID"` // For join query result
|
||||
Excerpt string `json:"excerpt" gorm:"column:excerpt"`
|
||||
Content string `json:"content" gorm:"column:content;type:text"`
|
||||
ReadCount uint `json:"readCount" gorm:"column:read_count;default:0"`
|
||||
@@ -51,15 +53,20 @@ func (p *Post) BeforeUpdate(tx *gorm.DB) error {
|
||||
|
||||
// PostResponse 博客文章响应模型
|
||||
type PostResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
CategoryID uint `json:"categoryId"`
|
||||
CategoryName string `json:"categoryName"`
|
||||
CategorySlug string `json:"categorySlug"`
|
||||
Date string `json:"date"`
|
||||
Excerpt string `json:"excerpt,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Tags []Tag `json:"tags,omitempty"`
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
CategoryID uint `json:"categoryId"`
|
||||
CategoryName string `json:"categoryName"`
|
||||
CategorySlug string `json:"categorySlug"`
|
||||
ColumnID *uint `json:"columnId,omitempty"`
|
||||
ColumnName string `json:"columnName,omitempty"`
|
||||
ColumnSlug string `json:"columnSlug,omitempty"`
|
||||
Date string `json:"date"`
|
||||
Excerpt string `json:"excerpt,omitempty"`
|
||||
Content *string `json:"content,omitempty"` // 使用指针类型,当 includeContent=true 时总是设置
|
||||
Tags []Tag `json:"tags,omitempty"`
|
||||
IsPublished int `json:"isPublished,omitempty"`
|
||||
ReadCount uint `json:"readCount,omitempty"`
|
||||
}
|
||||
|
||||
// PostHistory 文章历史记录模型
|
||||
|
||||
35
server/models/search_log.go
Normal file
35
server/models/search_log.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SearchLog 搜索记录模型
|
||||
type SearchLog struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Keyword string `json:"keyword" gorm:"column:keyword"`
|
||||
SearchType string `json:"searchType" gorm:"column:search_type"` // category/tag/column/keyword
|
||||
UserIP string `json:"userIp" gorm:"column:user_ip"`
|
||||
UserLocation string `json:"userLocation" gorm:"column:user_location"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (SearchLog) TableName() string {
|
||||
return "search_logs"
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前钩子
|
||||
func (sl *SearchLog) BeforeCreate(tx *gorm.DB) error {
|
||||
now := time.Now().Unix()
|
||||
if sl.CreatedAt == 0 {
|
||||
sl.CreatedAt = now
|
||||
}
|
||||
if sl.DeletedAt == 0 {
|
||||
sl.DeletedAt = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -41,7 +41,7 @@ CREATE TABLE `about_profiles` (
|
||||
-- ----------------------------
|
||||
-- Records of about_profiles
|
||||
-- ----------------------------
|
||||
INSERT INTO `about_profiles` VALUES (3, '年糕崽崽', 'https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4', '中国 · 浙江杭州', '嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。从早期的 jQuery 插件开发到如今的 WebGL 3D 场景构建,我始终认为:<strong>技术是骨架,艺术是灵魂。</strong><br><br>目前,我专注于高性能 B 端应用的体验升级,以及探索生成式艺术(Generative Art)在网页中的应用。闲暇之余,我喜欢在杭州的西湖边散步,或者捣腾我的客制化机械键盘。', 'liqiworker@gmail.com', 'ngzz_0218', '[\\\"Vue 3\\\",\\\"React\\\",\\\"TypeScript\\\",\\\"Three.js\\\",\\\"Golang\\\",\\\"Tailwind CSS\\\",\\\"Rust\\\",\\\"Wails\\\"]', '[{\\\"year\\\":\\\"2024 - 至今\\\",\\\"role\\\":\\\"技术负责人\\\",\\\"company\\\":\\\"某医疗平台公司\\\"},{\\\"year\\\":\\\"2020 - 2024\\\",\\\"role\\\":\\\"PHP开发工程师\\\",\\\"company\\\":\\\"某电商公司\\\"}]', 0, 0, 0, 0);
|
||||
INSERT IGNORE INTO `about_profiles` (`id`, `name`, `avatar`, `location`, `bio`, `email`, `wechat`, `tech_stack`, `experiences`, `is_primary`, `created_at`, `updated_at`, `deleted_at`) VALUES (3, '年糕崽崽', 'https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4', '中国 · 浙江杭州', '嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。从早期的 jQuery 插件开发到如今的 WebGL 3D 场景构建,我始终认为:<strong>技术是骨架,艺术是灵魂。</strong><br><br>目前,我专注于高性能 B 端应用的体验升级,以及探索生成式艺术(Generative Art)在网页中的应用。闲暇之余,我喜欢在杭州的西湖边散步,或者捣腾我的客制化机械键盘。', 'liqiworker@gmail.com', 'ngzz_0218', '[\\\"Vue 3\\\",\\\"React\\\",\\\"TypeScript\\\",\\\"Three.js\\\",\\\"Golang\\\",\\\"Tailwind CSS\\\",\\\"Rust\\\",\\\"Wails\\\"]', '[{\\\"year\\\":\\\"2024 - 至今\\\",\\\"role\\\":\\\"技术负责人\\\",\\\"company\\\":\\\"某医疗平台公司\\\"},{\\\"year\\\":\\\"2020 - 2024\\\",\\\"role\\\":\\\"PHP开发工程师\\\",\\\"company\\\":\\\"某电商公司\\\"}]', 0, 0, 0, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for access_logs
|
||||
@@ -191,7 +191,10 @@ CREATE TABLE `inquiries` (
|
||||
-- ----------------------------
|
||||
-- Table structure for operation_logs
|
||||
-- ----------------------------
|
||||
-- Ensure table is dropped even if it has dependencies
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
DROP TABLE IF EXISTS `operation_logs`;
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
CREATE TABLE `operation_logs` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` bigint UNSIGNED NOT NULL COMMENT '操作用户ID',
|
||||
@@ -208,653 +211,6 @@ CREATE TABLE `operation_logs` (
|
||||
INDEX `idx_user_id`(`user_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 644 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of operation_logs
|
||||
-- ----------------------------
|
||||
INSERT INTO `operation_logs` VALUES (1, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 403, 5, 0, 1768452895);
|
||||
INSERT INTO `operation_logs` VALUES (2, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, 0, 1768455285);
|
||||
INSERT INTO `operation_logs` VALUES (3, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 57, 0, 1768455285);
|
||||
INSERT INTO `operation_logs` VALUES (4, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 51, 0, 1768455286);
|
||||
INSERT INTO `operation_logs` VALUES (5, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768455290);
|
||||
INSERT INTO `operation_logs` VALUES (6, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 140, 0, 1768455293);
|
||||
INSERT INTO `operation_logs` VALUES (7, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768455294);
|
||||
INSERT INTO `operation_logs` VALUES (8, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 236, 0, 1768455295);
|
||||
INSERT INTO `operation_logs` VALUES (9, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 45, 0, 1768455295);
|
||||
INSERT INTO `operation_logs` VALUES (10, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 46, 0, 1768455296);
|
||||
INSERT INTO `operation_logs` VALUES (11, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768455297);
|
||||
INSERT INTO `operation_logs` VALUES (12, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 50, 0, 1768455301);
|
||||
INSERT INTO `operation_logs` VALUES (13, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, 0, 1768455301);
|
||||
INSERT INTO `operation_logs` VALUES (14, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 54, 0, 1768455437);
|
||||
INSERT INTO `operation_logs` VALUES (15, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 51, 0, 1768455437);
|
||||
INSERT INTO `operation_logs` VALUES (16, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 59, 0, 1768462099);
|
||||
INSERT INTO `operation_logs` VALUES (17, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 50, 0, 1768462099);
|
||||
INSERT INTO `operation_logs` VALUES (18, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 47, 0, 1768462100);
|
||||
INSERT INTO `operation_logs` VALUES (19, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 48, 0, 1768462102);
|
||||
INSERT INTO `operation_logs` VALUES (20, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768462103);
|
||||
INSERT INTO `operation_logs` VALUES (21, 1, 'lq', '::1', '/api/admin/users', 'POST', '{\"username\":\"cs\",\"email\":\"cs@nailaoyun.cn\",\"role\":\"viewer\",\"isActive\":1}', 200, 101, 0, 1768462115);
|
||||
INSERT INTO `operation_logs` VALUES (22, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768462115);
|
||||
INSERT INTO `operation_logs` VALUES (23, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 46, 0, 1768462120);
|
||||
INSERT INTO `operation_logs` VALUES (24, 1, 'lq', '::1', '/api/admin/users/2', 'PUT', '{\"username\":\"editor\",\"email\":\"editor@example.com\",\"role\":\"viewer\",\"isActive\":1}', 200, 97, 0, 1768462124);
|
||||
INSERT INTO `operation_logs` VALUES (25, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 45, 0, 1768462124);
|
||||
INSERT INTO `operation_logs` VALUES (26, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 46, 0, 1768462127);
|
||||
INSERT INTO `operation_logs` VALUES (27, 1, 'lq', '::1', '/api/admin/users/2', 'PUT', '{\"username\":\"editor\",\"email\":\"editor@example.com\",\"role\":\"editor\",\"isActive\":1}', 200, 98, 0, 1768462129);
|
||||
INSERT INTO `operation_logs` VALUES (28, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 45, 0, 1768462129);
|
||||
INSERT INTO `operation_logs` VALUES (29, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 132, 0, 1768462130);
|
||||
INSERT INTO `operation_logs` VALUES (30, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768462131);
|
||||
INSERT INTO `operation_logs` VALUES (31, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 237, 0, 1768462132);
|
||||
INSERT INTO `operation_logs` VALUES (32, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 45, 0, 1768462133);
|
||||
INSERT INTO `operation_logs` VALUES (33, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768462134);
|
||||
INSERT INTO `operation_logs` VALUES (34, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768462134);
|
||||
INSERT INTO `operation_logs` VALUES (35, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, 0, 1768462135);
|
||||
INSERT INTO `operation_logs` VALUES (36, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 50, 0, 1768462135);
|
||||
INSERT INTO `operation_logs` VALUES (37, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 47, 0, 1768462135);
|
||||
INSERT INTO `operation_logs` VALUES (38, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 55, 0, 1768462504);
|
||||
INSERT INTO `operation_logs` VALUES (39, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 54, 0, 1768462504);
|
||||
INSERT INTO `operation_logs` VALUES (40, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 241, 0, 1768462507);
|
||||
INSERT INTO `operation_logs` VALUES (41, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 44, 0, 1768462508);
|
||||
INSERT INTO `operation_logs` VALUES (42, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768462509);
|
||||
INSERT INTO `operation_logs` VALUES (43, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 47, 0, 1768462509);
|
||||
INSERT INTO `operation_logs` VALUES (44, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768462512);
|
||||
INSERT INTO `operation_logs` VALUES (45, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768462514);
|
||||
INSERT INTO `operation_logs` VALUES (46, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 46, 0, 1768462524);
|
||||
INSERT INTO `operation_logs` VALUES (47, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 44, 0, 1768462526);
|
||||
INSERT INTO `operation_logs` VALUES (48, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 44, 0, 1768462527);
|
||||
INSERT INTO `operation_logs` VALUES (49, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 143, 0, 1768462596);
|
||||
INSERT INTO `operation_logs` VALUES (50, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 49, 0, 1768462841);
|
||||
INSERT INTO `operation_logs` VALUES (51, 1, 'lq', '::1', '/api/admin/about/2', 'DELETE', '', 200, 42, 0, 1768462847);
|
||||
INSERT INTO `operation_logs` VALUES (52, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 42, 0, 1768462847);
|
||||
INSERT INTO `operation_logs` VALUES (53, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 40, 0, 1768462848);
|
||||
INSERT INTO `operation_logs` VALUES (54, 1, 'lq', '::1', '/api/admin/about/1', 'PUT', '{\"name\":\"年糕崽崽\",\"avatar\":\"https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4\",\"location\":\"中国 · 浙江杭州\",\"bio\":\"嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。从早期的 jQuery 插件开发到如今的 WebGL 3D 场景构建,我始终认为:<strong>技术是骨架,艺术是灵魂。</strong><br><br>目前,我专注于高性能 B 端应用的体验升级,以及探索生成式艺术(Generative Art)在网页中的应用。闲暇之余,我喜欢在杭州的西湖边散步,或者捣腾我的客制化机械键盘。\",\"email\":\"hello@niangao.dev\",\"wechat\":\"Niangao_Dev\",\"isPrimary\":true,\"experiences\":[{\"year\":\"2024 - 至今\",\"role\":\"技术负责人\",\"company\":\"某医疗平台公司\"},{\"year\":\"2020 - 2024\",\"role\":\"PHP开发工程师\",\"company\":\"某电商公司\"}],\"techStack\":[\"Vue 3\",\"React\",\"TypeScript\",\"Three.js\",\"Golang\",\"Tailwind CSS\",\"Rust\",\"Wails\"]}', 200, 55, 0, 1768462911);
|
||||
INSERT INTO `operation_logs` VALUES (55, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768462911);
|
||||
INSERT INTO `operation_logs` VALUES (56, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 45, 0, 1768462919);
|
||||
INSERT INTO `operation_logs` VALUES (57, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 221, 0, 1768462919);
|
||||
INSERT INTO `operation_logs` VALUES (58, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 45, 0, 1768462921);
|
||||
INSERT INTO `operation_logs` VALUES (59, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 131, 0, 1768462922);
|
||||
INSERT INTO `operation_logs` VALUES (60, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 44, 0, 1768462923);
|
||||
INSERT INTO `operation_logs` VALUES (61, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 42, 0, 1768462924);
|
||||
INSERT INTO `operation_logs` VALUES (62, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, 0, 1768462924);
|
||||
INSERT INTO `operation_logs` VALUES (63, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 42, 0, 1768462927);
|
||||
INSERT INTO `operation_logs` VALUES (64, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768464953);
|
||||
INSERT INTO `operation_logs` VALUES (65, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 51, 0, 1768464953);
|
||||
INSERT INTO `operation_logs` VALUES (66, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768464956);
|
||||
INSERT INTO `operation_logs` VALUES (67, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 51, 0, 1768464991);
|
||||
INSERT INTO `operation_logs` VALUES (68, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, 0, 1768464991);
|
||||
INSERT INTO `operation_logs` VALUES (69, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768464993);
|
||||
INSERT INTO `operation_logs` VALUES (70, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768464993);
|
||||
INSERT INTO `operation_logs` VALUES (71, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768464994);
|
||||
INSERT INTO `operation_logs` VALUES (72, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 239, 0, 1768464995);
|
||||
INSERT INTO `operation_logs` VALUES (73, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768464996);
|
||||
INSERT INTO `operation_logs` VALUES (74, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 51, 0, 1768464996);
|
||||
INSERT INTO `operation_logs` VALUES (75, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768464997);
|
||||
INSERT INTO `operation_logs` VALUES (76, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 50, 0, 1768464997);
|
||||
INSERT INTO `operation_logs` VALUES (77, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768464998);
|
||||
INSERT INTO `operation_logs` VALUES (78, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768464998);
|
||||
INSERT INTO `operation_logs` VALUES (79, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 48, 0, 1768464999);
|
||||
INSERT INTO `operation_logs` VALUES (80, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768465116);
|
||||
INSERT INTO `operation_logs` VALUES (81, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 54, 0, 1768465116);
|
||||
INSERT INTO `operation_logs` VALUES (82, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, 0, 1768465120);
|
||||
INSERT INTO `operation_logs` VALUES (83, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 52, 0, 1768465156);
|
||||
INSERT INTO `operation_logs` VALUES (84, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768465157);
|
||||
INSERT INTO `operation_logs` VALUES (85, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 46, 0, 1768465157);
|
||||
INSERT INTO `operation_logs` VALUES (86, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768465937);
|
||||
INSERT INTO `operation_logs` VALUES (87, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768465938);
|
||||
INSERT INTO `operation_logs` VALUES (88, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768483051);
|
||||
INSERT INTO `operation_logs` VALUES (89, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 93, 0, 1768483051);
|
||||
INSERT INTO `operation_logs` VALUES (90, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 240, 0, 1768483066);
|
||||
INSERT INTO `operation_logs` VALUES (91, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 50, 0, 1768483074);
|
||||
INSERT INTO `operation_logs` VALUES (92, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, 0, 1768524774);
|
||||
INSERT INTO `operation_logs` VALUES (93, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 47, 0, 1768524774);
|
||||
INSERT INTO `operation_logs` VALUES (94, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 46, 0, 1768524784);
|
||||
INSERT INTO `operation_logs` VALUES (95, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 50, 0, 1768525274);
|
||||
INSERT INTO `operation_logs` VALUES (96, 1, 'lq', '::1', '/api/admin/inquiries/1/status', 'PUT', '{\"status\":1}', 200, 49, 0, 1768525276);
|
||||
INSERT INTO `operation_logs` VALUES (97, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 46, 0, 1768525276);
|
||||
INSERT INTO `operation_logs` VALUES (98, 1, 'lq', '::1', '/api/admin/inquiries/1/status', 'PUT', '{\"status\":2}', 200, 49, 0, 1768525280);
|
||||
INSERT INTO `operation_logs` VALUES (99, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 45, 0, 1768525280);
|
||||
INSERT INTO `operation_logs` VALUES (100, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 47, 0, 1768525377);
|
||||
INSERT INTO `operation_logs` VALUES (101, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, 0, 1768525429);
|
||||
INSERT INTO `operation_logs` VALUES (102, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, 0, 1768525429);
|
||||
INSERT INTO `operation_logs` VALUES (103, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768525435);
|
||||
INSERT INTO `operation_logs` VALUES (104, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 43, 0, 1768525437);
|
||||
INSERT INTO `operation_logs` VALUES (105, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768525516);
|
||||
INSERT INTO `operation_logs` VALUES (106, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768525522);
|
||||
INSERT INTO `operation_logs` VALUES (107, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 49, 0, 1768525522);
|
||||
INSERT INTO `operation_logs` VALUES (108, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768526026);
|
||||
INSERT INTO `operation_logs` VALUES (109, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 227, 0, 1768526028);
|
||||
INSERT INTO `operation_logs` VALUES (110, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 46, 0, 1768526029);
|
||||
INSERT INTO `operation_logs` VALUES (111, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 43, 0, 1768526030);
|
||||
INSERT INTO `operation_logs` VALUES (112, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 45, 0, 1768526031);
|
||||
INSERT INTO `operation_logs` VALUES (113, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 50, 0, 1768526039);
|
||||
INSERT INTO `operation_logs` VALUES (114, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 45, 0, 1768526046);
|
||||
INSERT INTO `operation_logs` VALUES (115, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 55, 0, 1768526110);
|
||||
INSERT INTO `operation_logs` VALUES (116, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 142, 0, 1768526116);
|
||||
INSERT INTO `operation_logs` VALUES (117, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 46, 0, 1768526119);
|
||||
INSERT INTO `operation_logs` VALUES (118, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 138, 0, 1768526120);
|
||||
INSERT INTO `operation_logs` VALUES (119, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 46, 0, 1768526123);
|
||||
INSERT INTO `operation_logs` VALUES (120, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768526129);
|
||||
INSERT INTO `operation_logs` VALUES (121, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768526130);
|
||||
INSERT INTO `operation_logs` VALUES (122, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 47, 0, 1768526133);
|
||||
INSERT INTO `operation_logs` VALUES (123, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 45, 0, 1768526133);
|
||||
INSERT INTO `operation_logs` VALUES (124, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 50, 0, 1768526708);
|
||||
INSERT INTO `operation_logs` VALUES (125, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 50, 0, 1768526708);
|
||||
INSERT INTO `operation_logs` VALUES (126, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 52, 0, 1768526723);
|
||||
INSERT INTO `operation_logs` VALUES (127, 1, 'lq', '::1', '/api/admin/dashboard/activities', 'GET', '', 200, 46, 0, 1768526723);
|
||||
INSERT INTO `operation_logs` VALUES (128, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 53, 0, 1768526783);
|
||||
INSERT INTO `operation_logs` VALUES (129, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, 0, 1768526783);
|
||||
INSERT INTO `operation_logs` VALUES (130, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 49, 0, 1768526973);
|
||||
INSERT INTO `operation_logs` VALUES (131, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768527108);
|
||||
INSERT INTO `operation_logs` VALUES (132, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 55, 0, 1768527379);
|
||||
INSERT INTO `operation_logs` VALUES (133, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768527425);
|
||||
INSERT INTO `operation_logs` VALUES (134, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768527425);
|
||||
INSERT INTO `operation_logs` VALUES (135, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 51, 0, 1768527502);
|
||||
INSERT INTO `operation_logs` VALUES (136, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 54, 0, 1768527595);
|
||||
INSERT INTO `operation_logs` VALUES (137, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 57, 0, 1768527595);
|
||||
INSERT INTO `operation_logs` VALUES (138, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 45, 0, 1768527605);
|
||||
INSERT INTO `operation_logs` VALUES (139, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 44, 0, 1768527608);
|
||||
INSERT INTO `operation_logs` VALUES (140, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, 0, 1768527614);
|
||||
INSERT INTO `operation_logs` VALUES (141, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, 0, 1768527625);
|
||||
INSERT INTO `operation_logs` VALUES (142, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 45, 0, 1768527653);
|
||||
INSERT INTO `operation_logs` VALUES (143, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 48, 0, 1768527658);
|
||||
INSERT INTO `operation_logs` VALUES (144, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 53, 0, 1768527880);
|
||||
INSERT INTO `operation_logs` VALUES (145, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, 0, 1768527885);
|
||||
INSERT INTO `operation_logs` VALUES (146, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 45, 0, 1768527886);
|
||||
INSERT INTO `operation_logs` VALUES (147, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 46, 0, 1768527887);
|
||||
INSERT INTO `operation_logs` VALUES (148, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 47, 0, 1768528036);
|
||||
INSERT INTO `operation_logs` VALUES (149, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 51, 0, 1768528091);
|
||||
INSERT INTO `operation_logs` VALUES (150, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 142, 0, 1768528656);
|
||||
INSERT INTO `operation_logs` VALUES (151, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 153, 0, 1768528669);
|
||||
INSERT INTO `operation_logs` VALUES (152, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 144, 0, 1768528671);
|
||||
INSERT INTO `operation_logs` VALUES (153, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 132, 0, 1768528672);
|
||||
INSERT INTO `operation_logs` VALUES (154, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, 0, 1768528674);
|
||||
INSERT INTO `operation_logs` VALUES (155, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 144, 0, 1768528674);
|
||||
INSERT INTO `operation_logs` VALUES (156, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, 0, 1768528675);
|
||||
INSERT INTO `operation_logs` VALUES (157, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 143, 0, 1768528676);
|
||||
INSERT INTO `operation_logs` VALUES (158, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 57, 0, 1768528682);
|
||||
INSERT INTO `operation_logs` VALUES (159, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 97, 0, 1768528682);
|
||||
INSERT INTO `operation_logs` VALUES (160, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 145, 0, 1768528684);
|
||||
INSERT INTO `operation_logs` VALUES (161, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768528684);
|
||||
INSERT INTO `operation_logs` VALUES (162, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 242, 0, 1768528685);
|
||||
INSERT INTO `operation_logs` VALUES (163, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 49, 0, 1768528687);
|
||||
INSERT INTO `operation_logs` VALUES (164, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 50, 0, 1768528688);
|
||||
INSERT INTO `operation_logs` VALUES (165, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768528690);
|
||||
INSERT INTO `operation_logs` VALUES (166, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 245, 0, 1768528693);
|
||||
INSERT INTO `operation_logs` VALUES (167, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768528694);
|
||||
INSERT INTO `operation_logs` VALUES (168, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 99, 0, 1768528694);
|
||||
INSERT INTO `operation_logs` VALUES (169, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 220, 0, 1768528698);
|
||||
INSERT INTO `operation_logs` VALUES (170, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, 0, 1768528699);
|
||||
INSERT INTO `operation_logs` VALUES (171, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, 0, 1768528773);
|
||||
INSERT INTO `operation_logs` VALUES (172, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 92, 0, 1768528773);
|
||||
INSERT INTO `operation_logs` VALUES (173, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 139, 0, 1768528774);
|
||||
INSERT INTO `operation_logs` VALUES (174, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 95, 0, 1768528932);
|
||||
INSERT INTO `operation_logs` VALUES (175, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 141, 0, 1768528935);
|
||||
INSERT INTO `operation_logs` VALUES (176, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, 0, 1768528937);
|
||||
INSERT INTO `operation_logs` VALUES (177, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, 0, 1768528937);
|
||||
INSERT INTO `operation_logs` VALUES (178, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 143, 0, 1768528941);
|
||||
INSERT INTO `operation_logs` VALUES (179, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 138, 0, 1768528942);
|
||||
INSERT INTO `operation_logs` VALUES (180, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, 0, 1768528943);
|
||||
INSERT INTO `operation_logs` VALUES (181, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 146, 0, 1768528951);
|
||||
INSERT INTO `operation_logs` VALUES (182, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768529007);
|
||||
INSERT INTO `operation_logs` VALUES (183, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, 0, 1768529007);
|
||||
INSERT INTO `operation_logs` VALUES (184, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768529012);
|
||||
INSERT INTO `operation_logs` VALUES (185, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 90, 0, 1768529012);
|
||||
INSERT INTO `operation_logs` VALUES (186, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 136, 0, 1768529013);
|
||||
INSERT INTO `operation_logs` VALUES (187, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 200, 0, 1768529364);
|
||||
INSERT INTO `operation_logs` VALUES (188, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 193, 0, 1768529373);
|
||||
INSERT INTO `operation_logs` VALUES (189, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 183, 0, 1768529459);
|
||||
INSERT INTO `operation_logs` VALUES (190, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 138, 0, 1768529618);
|
||||
INSERT INTO `operation_logs` VALUES (191, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, 0, 1768529620);
|
||||
INSERT INTO `operation_logs` VALUES (192, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 141, 0, 1768529790);
|
||||
INSERT INTO `operation_logs` VALUES (193, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, 0, 1768529867);
|
||||
INSERT INTO `operation_logs` VALUES (194, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 95, 0, 1768529867);
|
||||
INSERT INTO `operation_logs` VALUES (195, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, 0, 1768529869);
|
||||
INSERT INTO `operation_logs` VALUES (196, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, 0, 1768530104);
|
||||
INSERT INTO `operation_logs` VALUES (197, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 90, 0, 1768530104);
|
||||
INSERT INTO `operation_logs` VALUES (198, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 90, 0, 1768530104);
|
||||
INSERT INTO `operation_logs` VALUES (199, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 53, 0, 1768532353);
|
||||
INSERT INTO `operation_logs` VALUES (200, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 103, 0, 1768532353);
|
||||
INSERT INTO `operation_logs` VALUES (201, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 93, 0, 1768532365);
|
||||
INSERT INTO `operation_logs` VALUES (202, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 55, 0, 1768532365);
|
||||
INSERT INTO `operation_logs` VALUES (203, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 146, 0, 1768532370);
|
||||
INSERT INTO `operation_logs` VALUES (204, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 49, 0, 1768532371);
|
||||
INSERT INTO `operation_logs` VALUES (205, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 237, 0, 1768532372);
|
||||
INSERT INTO `operation_logs` VALUES (206, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, 0, 1768532375);
|
||||
INSERT INTO `operation_logs` VALUES (207, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 47, 0, 1768532375);
|
||||
INSERT INTO `operation_logs` VALUES (208, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 46, 0, 1768532380);
|
||||
INSERT INTO `operation_logs` VALUES (209, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 45, 0, 1768532385);
|
||||
INSERT INTO `operation_logs` VALUES (210, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 46, 0, 1768532386);
|
||||
INSERT INTO `operation_logs` VALUES (211, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 46, 0, 1768532405);
|
||||
INSERT INTO `operation_logs` VALUES (212, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 53, 0, 1768532407);
|
||||
INSERT INTO `operation_logs` VALUES (213, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768532409);
|
||||
INSERT INTO `operation_logs` VALUES (214, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 147, 0, 1768532410);
|
||||
INSERT INTO `operation_logs` VALUES (215, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 45, 0, 1768532440);
|
||||
INSERT INTO `operation_logs` VALUES (216, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768532441);
|
||||
INSERT INTO `operation_logs` VALUES (217, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, 0, 1768532443);
|
||||
INSERT INTO `operation_logs` VALUES (218, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 146, 0, 1768532446);
|
||||
INSERT INTO `operation_logs` VALUES (219, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 150, 0, 1768532460);
|
||||
INSERT INTO `operation_logs` VALUES (220, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768532475);
|
||||
INSERT INTO `operation_logs` VALUES (221, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 142, 0, 1768532475);
|
||||
INSERT INTO `operation_logs` VALUES (222, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 148, 0, 1768532520);
|
||||
INSERT INTO `operation_logs` VALUES (223, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768532585);
|
||||
INSERT INTO `operation_logs` VALUES (224, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, 0, 1768532585);
|
||||
INSERT INTO `operation_logs` VALUES (225, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 93, 0, 1768532681);
|
||||
INSERT INTO `operation_logs` VALUES (226, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768532681);
|
||||
INSERT INTO `operation_logs` VALUES (227, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768532725);
|
||||
INSERT INTO `operation_logs` VALUES (228, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 100, 0, 1768532725);
|
||||
INSERT INTO `operation_logs` VALUES (229, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768532738);
|
||||
INSERT INTO `operation_logs` VALUES (230, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 95, 0, 1768532738);
|
||||
INSERT INTO `operation_logs` VALUES (231, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 94, 0, 1768532910);
|
||||
INSERT INTO `operation_logs` VALUES (232, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 50, 0, 1768532910);
|
||||
INSERT INTO `operation_logs` VALUES (233, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 49, 0, 1768532912);
|
||||
INSERT INTO `operation_logs` VALUES (234, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 46, 0, 1768532914);
|
||||
INSERT INTO `operation_logs` VALUES (235, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768532915);
|
||||
INSERT INTO `operation_logs` VALUES (236, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 46, 0, 1768532917);
|
||||
INSERT INTO `operation_logs` VALUES (237, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768532919);
|
||||
INSERT INTO `operation_logs` VALUES (238, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 51, 0, 1768533052);
|
||||
INSERT INTO `operation_logs` VALUES (239, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 246, 0, 1768533154);
|
||||
INSERT INTO `operation_logs` VALUES (240, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768533159);
|
||||
INSERT INTO `operation_logs` VALUES (241, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768533167);
|
||||
INSERT INTO `operation_logs` VALUES (242, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 44, 0, 1768533173);
|
||||
INSERT INTO `operation_logs` VALUES (243, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 46, 0, 1768533202);
|
||||
INSERT INTO `operation_logs` VALUES (244, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 92, 0, 1768533202);
|
||||
INSERT INTO `operation_logs` VALUES (245, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 94, 0, 1768533212);
|
||||
INSERT INTO `operation_logs` VALUES (246, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 55, 0, 1768533212);
|
||||
INSERT INTO `operation_logs` VALUES (247, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 46, 0, 1768533214);
|
||||
INSERT INTO `operation_logs` VALUES (248, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 137, 0, 1768533222);
|
||||
INSERT INTO `operation_logs` VALUES (249, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 143, 0, 1768533226);
|
||||
INSERT INTO `operation_logs` VALUES (250, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 92, 0, 1768533229);
|
||||
INSERT INTO `operation_logs` VALUES (251, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768533229);
|
||||
INSERT INTO `operation_logs` VALUES (252, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 47, 0, 1768533238);
|
||||
INSERT INTO `operation_logs` VALUES (253, 1, 'lq', '::1', '/api/admin/users/2', 'GET', '', 200, 48, 0, 1768533242);
|
||||
INSERT INTO `operation_logs` VALUES (254, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 45, 0, 1768533244);
|
||||
INSERT INTO `operation_logs` VALUES (255, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 96, 0, 1768533265);
|
||||
INSERT INTO `operation_logs` VALUES (256, 5, 'cs', '::1', '/api/admin/operation-logs', 'GET', '', 200, 51, 0, 1768533265);
|
||||
INSERT INTO `operation_logs` VALUES (257, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 142, 0, 1768533270);
|
||||
INSERT INTO `operation_logs` VALUES (258, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 233, 0, 1768533271);
|
||||
INSERT INTO `operation_logs` VALUES (259, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 45, 0, 1768533272);
|
||||
INSERT INTO `operation_logs` VALUES (260, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 219, 0, 1768533361);
|
||||
INSERT INTO `operation_logs` VALUES (261, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 221, 0, 1768533366);
|
||||
INSERT INTO `operation_logs` VALUES (262, 5, 'cs', '::1', '/api/admin/snippets', 'GET', '', 200, 48, 0, 1768533370);
|
||||
INSERT INTO `operation_logs` VALUES (263, 5, 'cs', '::1', '/api/admin/tags', 'GET', '', 200, 44, 0, 1768533371);
|
||||
INSERT INTO `operation_logs` VALUES (264, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 45, 0, 1768533372);
|
||||
INSERT INTO `operation_logs` VALUES (265, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 139, 0, 1768533373);
|
||||
INSERT INTO `operation_logs` VALUES (266, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 153, 0, 1768533735);
|
||||
INSERT INTO `operation_logs` VALUES (267, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 183, 0, 1768533846);
|
||||
INSERT INTO `operation_logs` VALUES (268, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 182, 0, 1768533850);
|
||||
INSERT INTO `operation_logs` VALUES (269, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 180, 0, 1768533851);
|
||||
INSERT INTO `operation_logs` VALUES (270, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 193, 0, 1768533852);
|
||||
INSERT INTO `operation_logs` VALUES (271, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 192, 0, 1768533854);
|
||||
INSERT INTO `operation_logs` VALUES (272, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 197, 0, 1768533930);
|
||||
INSERT INTO `operation_logs` VALUES (273, 5, 'cs', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 46, 0, 1768534851);
|
||||
INSERT INTO `operation_logs` VALUES (274, 5, 'cs', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 47, 0, 1768535295);
|
||||
INSERT INTO `operation_logs` VALUES (275, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 189, 0, 1768535879);
|
||||
INSERT INTO `operation_logs` VALUES (276, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 190, 0, 1768535946);
|
||||
INSERT INTO `operation_logs` VALUES (277, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 190, 0, 1768535948);
|
||||
INSERT INTO `operation_logs` VALUES (278, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 189, 0, 1768535951);
|
||||
INSERT INTO `operation_logs` VALUES (279, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 196, 0, 1768535953);
|
||||
INSERT INTO `operation_logs` VALUES (280, 5, 'cs', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768537859);
|
||||
INSERT INTO `operation_logs` VALUES (281, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 50, 0, 1768537860);
|
||||
INSERT INTO `operation_logs` VALUES (282, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 250, 0, 1768538179);
|
||||
INSERT INTO `operation_logs` VALUES (283, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768538180);
|
||||
INSERT INTO `operation_logs` VALUES (284, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 224, 0, 1768538193);
|
||||
INSERT INTO `operation_logs` VALUES (285, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768538193);
|
||||
INSERT INTO `operation_logs` VALUES (286, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 245, 0, 1768539279);
|
||||
INSERT INTO `operation_logs` VALUES (287, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 49, 0, 1768539282);
|
||||
INSERT INTO `operation_logs` VALUES (288, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768539301);
|
||||
INSERT INTO `operation_logs` VALUES (289, 5, 'cs', '::1', '/api/admin/works', 'GET', '', 200, 236, 0, 1768539305);
|
||||
INSERT INTO `operation_logs` VALUES (290, 5, 'cs', '::1', '/api/admin/snippets', 'GET', '', 200, 43, 0, 1768539306);
|
||||
INSERT INTO `operation_logs` VALUES (291, 5, 'cs', '::1', '/api/admin/tags', 'GET', '', 200, 45, 0, 1768539308);
|
||||
INSERT INTO `operation_logs` VALUES (292, 5, 'cs', '::1', '/api/admin/snippets', 'GET', '', 200, 48, 0, 1768539309);
|
||||
INSERT INTO `operation_logs` VALUES (293, 5, 'cs', '::1', '/api/admin/posts', 'GET', '', 200, 53, 0, 1768539311);
|
||||
INSERT INTO `operation_logs` VALUES (294, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 306, 0, 1768539314);
|
||||
INSERT INTO `operation_logs` VALUES (295, 5, 'cs', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 299, 0, 1768539320);
|
||||
INSERT INTO `operation_logs` VALUES (296, 5, 'cs', '::1', '/api/admin/about', 'GET', '', 200, 46, 0, 1768539435);
|
||||
INSERT INTO `operation_logs` VALUES (297, 5, 'cs', '::1', '/api/admin/about', 'GET', '', 200, 62, 0, 1768539635);
|
||||
INSERT INTO `operation_logs` VALUES (298, 5, 'cs', '::1', '/api/admin/about', 'GET', '', 200, 53, 0, 1768539805);
|
||||
INSERT INTO `operation_logs` VALUES (299, 5, 'cs', '::1', '/api/admin/testimonials', 'POST', '{\"author\":\"李二狗\",\"role\":\"CTP\",\"avatar\":\"\",\"rating\":5,\"content\":\"服务很贴心\"}', 403, 0, 0, 1768539861);
|
||||
INSERT INTO `operation_logs` VALUES (300, 5, 'cs', '::1', '/api/admin/testimonials', 'POST', '{\"author\":\"李二狗\",\"role\":\"CTP\",\"avatar\":\"https://api.dicebear.com/7.x/avataaars/svg?seed=David\",\"rating\":5,\"content\":\"服务很贴心\"}', 403, 3, 0, 1768539894);
|
||||
INSERT INTO `operation_logs` VALUES (301, 5, 'cs', '::1', '/api/admin/testimonials', 'POST', '{\"author\":\"李二狗\",\"role\":\"CTO\",\"avatar\":\"https://api.dicebear.com/7.x/avataaars/svg?seed=David\",\"rating\":5,\"content\":\"服务很贴心\"}', 403, 1, 0, 1768539901);
|
||||
INSERT INTO `operation_logs` VALUES (302, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 55, 0, 1768539911);
|
||||
INSERT INTO `operation_logs` VALUES (303, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 230, 0, 1768539911);
|
||||
INSERT INTO `operation_logs` VALUES (304, 1, 'lq', '::1', '/api/admin/testimonials', 'POST', '{\"author\":\"王甜甜\",\"role\":\"CTO\",\"avatar\":\"https://api.dicebear.com/7.x/avataaars/svg?seed=David\",\"rating\":5,\"content\":\"服务很贴心,技术够硬\"}', 200, 56, 0, 1768539952);
|
||||
INSERT INTO `operation_logs` VALUES (305, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 49, 0, 1768539988);
|
||||
INSERT INTO `operation_logs` VALUES (306, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 49, 0, 1768540005);
|
||||
INSERT INTO `operation_logs` VALUES (307, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 50, 0, 1768545569);
|
||||
INSERT INTO `operation_logs` VALUES (308, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 52, 0, 1768545632);
|
||||
INSERT INTO `operation_logs` VALUES (309, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 245, 0, 1768545777);
|
||||
INSERT INTO `operation_logs` VALUES (310, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 48, 0, 1768545778);
|
||||
INSERT INTO `operation_logs` VALUES (311, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 49, 0, 1768545778);
|
||||
INSERT INTO `operation_logs` VALUES (312, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 50, 0, 1768545851);
|
||||
INSERT INTO `operation_logs` VALUES (313, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 49, 0, 1768545853);
|
||||
INSERT INTO `operation_logs` VALUES (314, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 50, 0, 1768546733);
|
||||
INSERT INTO `operation_logs` VALUES (315, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 49, 0, 1768546740);
|
||||
INSERT INTO `operation_logs` VALUES (316, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768546748);
|
||||
INSERT INTO `operation_logs` VALUES (317, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 56, 0, 1768547782);
|
||||
INSERT INTO `operation_logs` VALUES (318, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 81, 0, 1768547782);
|
||||
INSERT INTO `operation_logs` VALUES (319, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768547862);
|
||||
INSERT INTO `operation_logs` VALUES (320, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 45, 0, 1768547864);
|
||||
INSERT INTO `operation_logs` VALUES (321, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 43, 0, 1768547868);
|
||||
INSERT INTO `operation_logs` VALUES (322, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 50, 0, 1768547870);
|
||||
INSERT INTO `operation_logs` VALUES (323, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 50, 0, 1768547871);
|
||||
INSERT INTO `operation_logs` VALUES (324, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 51, 0, 1768547874);
|
||||
INSERT INTO `operation_logs` VALUES (325, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 242, 0, 1768547875);
|
||||
INSERT INTO `operation_logs` VALUES (326, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 46, 0, 1768547876);
|
||||
INSERT INTO `operation_logs` VALUES (327, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 265, 0, 1768547877);
|
||||
INSERT INTO `operation_logs` VALUES (328, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 46, 0, 1768547879);
|
||||
INSERT INTO `operation_logs` VALUES (329, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 49, 0, 1768547880);
|
||||
INSERT INTO `operation_logs` VALUES (330, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 52, 0, 1768547945);
|
||||
INSERT INTO `operation_logs` VALUES (331, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 49, 0, 1768547948);
|
||||
INSERT INTO `operation_logs` VALUES (332, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 246, 0, 1768547984);
|
||||
INSERT INTO `operation_logs` VALUES (333, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 49, 0, 1768547984);
|
||||
INSERT INTO `operation_logs` VALUES (334, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 46, 0, 1768547985);
|
||||
INSERT INTO `operation_logs` VALUES (335, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 48, 0, 1768547987);
|
||||
INSERT INTO `operation_logs` VALUES (336, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 52, 0, 1768548022);
|
||||
INSERT INTO `operation_logs` VALUES (337, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 258, 0, 1768548025);
|
||||
INSERT INTO `operation_logs` VALUES (338, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 46, 0, 1768548026);
|
||||
INSERT INTO `operation_logs` VALUES (339, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 46, 0, 1768548027);
|
||||
INSERT INTO `operation_logs` VALUES (340, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 46, 0, 1768548068);
|
||||
INSERT INTO `operation_logs` VALUES (341, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 44, 0, 1768548070);
|
||||
INSERT INTO `operation_logs` VALUES (342, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 224, 0, 1768548071);
|
||||
INSERT INTO `operation_logs` VALUES (343, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 45, 0, 1768548072);
|
||||
INSERT INTO `operation_logs` VALUES (344, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 41, 0, 1768548074);
|
||||
INSERT INTO `operation_logs` VALUES (345, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 242, 0, 1768548075);
|
||||
INSERT INTO `operation_logs` VALUES (346, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 48, 0, 1768548076);
|
||||
INSERT INTO `operation_logs` VALUES (347, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 50, 0, 1768548079);
|
||||
INSERT INTO `operation_logs` VALUES (348, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 46, 0, 1768548081);
|
||||
INSERT INTO `operation_logs` VALUES (349, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 243, 0, 1768548112);
|
||||
INSERT INTO `operation_logs` VALUES (350, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 47, 0, 1768548114);
|
||||
INSERT INTO `operation_logs` VALUES (351, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 46, 0, 1768548121);
|
||||
INSERT INTO `operation_logs` VALUES (352, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 47, 0, 1768548126);
|
||||
INSERT INTO `operation_logs` VALUES (353, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 50, 0, 1768548128);
|
||||
INSERT INTO `operation_logs` VALUES (354, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 53, 0, 1768548131);
|
||||
INSERT INTO `operation_logs` VALUES (355, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 47, 0, 1768548178);
|
||||
INSERT INTO `operation_logs` VALUES (356, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 44, 0, 1768548279);
|
||||
INSERT INTO `operation_logs` VALUES (357, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 53, 0, 1768548445);
|
||||
INSERT INTO `operation_logs` VALUES (358, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768548447);
|
||||
INSERT INTO `operation_logs` VALUES (359, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 52, 0, 1768548449);
|
||||
INSERT INTO `operation_logs` VALUES (360, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 48, 0, 1768548940);
|
||||
INSERT INTO `operation_logs` VALUES (361, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 48, 0, 1768548945);
|
||||
INSERT INTO `operation_logs` VALUES (362, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 49, 0, 1768549669);
|
||||
INSERT INTO `operation_logs` VALUES (363, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, 0, 1768549670);
|
||||
INSERT INTO `operation_logs` VALUES (364, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 49, 0, 1768549671);
|
||||
INSERT INTO `operation_logs` VALUES (365, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 47, 0, 1768549672);
|
||||
INSERT INTO `operation_logs` VALUES (366, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768549673);
|
||||
INSERT INTO `operation_logs` VALUES (367, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 47, 0, 1768549674);
|
||||
INSERT INTO `operation_logs` VALUES (368, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 49, 0, 1768549676);
|
||||
INSERT INTO `operation_logs` VALUES (369, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 50, 0, 1768549769);
|
||||
INSERT INTO `operation_logs` VALUES (370, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 54, 0, 1768549974);
|
||||
INSERT INTO `operation_logs` VALUES (371, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 247, 0, 1768549974);
|
||||
INSERT INTO `operation_logs` VALUES (372, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 289, 0, 1768549979);
|
||||
INSERT INTO `operation_logs` VALUES (373, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 51, 0, 1768549983);
|
||||
INSERT INTO `operation_logs` VALUES (374, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 51, 0, 1768549985);
|
||||
INSERT INTO `operation_logs` VALUES (375, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 59, 0, 1768549988);
|
||||
INSERT INTO `operation_logs` VALUES (376, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 51, 0, 1768549992);
|
||||
INSERT INTO `operation_logs` VALUES (377, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 150, 0, 1768550154);
|
||||
INSERT INTO `operation_logs` VALUES (378, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 141, 0, 1768550221);
|
||||
INSERT INTO `operation_logs` VALUES (379, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768550224);
|
||||
INSERT INTO `operation_logs` VALUES (380, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 138, 0, 1768550226);
|
||||
INSERT INTO `operation_logs` VALUES (381, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 141, 0, 1768550228);
|
||||
INSERT INTO `operation_logs` VALUES (382, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 144, 0, 1768550229);
|
||||
INSERT INTO `operation_logs` VALUES (383, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 148, 0, 1768550231);
|
||||
INSERT INTO `operation_logs` VALUES (384, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 146, 0, 1768550233);
|
||||
INSERT INTO `operation_logs` VALUES (385, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 141, 0, 1768550234);
|
||||
INSERT INTO `operation_logs` VALUES (386, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 144, 0, 1768550236);
|
||||
INSERT INTO `operation_logs` VALUES (387, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768550237);
|
||||
INSERT INTO `operation_logs` VALUES (388, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768550239);
|
||||
INSERT INTO `operation_logs` VALUES (389, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 144, 0, 1768550241);
|
||||
INSERT INTO `operation_logs` VALUES (390, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 146, 0, 1768550247);
|
||||
INSERT INTO `operation_logs` VALUES (391, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 142, 0, 1768550254);
|
||||
INSERT INTO `operation_logs` VALUES (392, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 141, 0, 1768550256);
|
||||
INSERT INTO `operation_logs` VALUES (393, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 131, 0, 1768550258);
|
||||
INSERT INTO `operation_logs` VALUES (394, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 133, 0, 1768550259);
|
||||
INSERT INTO `operation_logs` VALUES (395, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 134, 0, 1768550261);
|
||||
INSERT INTO `operation_logs` VALUES (396, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 144, 0, 1768550264);
|
||||
INSERT INTO `operation_logs` VALUES (397, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 147, 0, 1768550271);
|
||||
INSERT INTO `operation_logs` VALUES (398, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 148, 0, 1768550274);
|
||||
INSERT INTO `operation_logs` VALUES (399, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 144, 0, 1768550276);
|
||||
INSERT INTO `operation_logs` VALUES (400, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 146, 0, 1768550277);
|
||||
INSERT INTO `operation_logs` VALUES (401, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 144, 0, 1768550280);
|
||||
INSERT INTO `operation_logs` VALUES (402, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 143, 0, 1768550282);
|
||||
INSERT INTO `operation_logs` VALUES (403, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 146, 0, 1768550285);
|
||||
INSERT INTO `operation_logs` VALUES (404, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768550288);
|
||||
INSERT INTO `operation_logs` VALUES (405, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 139, 0, 1768550293);
|
||||
INSERT INTO `operation_logs` VALUES (406, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 143, 0, 1768550300);
|
||||
INSERT INTO `operation_logs` VALUES (407, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768550302);
|
||||
INSERT INTO `operation_logs` VALUES (408, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 141, 0, 1768550306);
|
||||
INSERT INTO `operation_logs` VALUES (409, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 158, 0, 1768550308);
|
||||
INSERT INTO `operation_logs` VALUES (410, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 150, 0, 1768550311);
|
||||
INSERT INTO `operation_logs` VALUES (411, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768550314);
|
||||
INSERT INTO `operation_logs` VALUES (412, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768550316);
|
||||
INSERT INTO `operation_logs` VALUES (413, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 144, 0, 1768550318);
|
||||
INSERT INTO `operation_logs` VALUES (414, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 143, 0, 1768550321);
|
||||
INSERT INTO `operation_logs` VALUES (415, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 144, 0, 1768550326);
|
||||
INSERT INTO `operation_logs` VALUES (416, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 133, 0, 1768550328);
|
||||
INSERT INTO `operation_logs` VALUES (417, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 132, 0, 1768550331);
|
||||
INSERT INTO `operation_logs` VALUES (418, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 141, 0, 1768550333);
|
||||
INSERT INTO `operation_logs` VALUES (419, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768550336);
|
||||
INSERT INTO `operation_logs` VALUES (420, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 142, 0, 1768550339);
|
||||
INSERT INTO `operation_logs` VALUES (421, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 149, 0, 1768550341);
|
||||
INSERT INTO `operation_logs` VALUES (422, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 152, 0, 1768550344);
|
||||
INSERT INTO `operation_logs` VALUES (423, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 155, 0, 1768550345);
|
||||
INSERT INTO `operation_logs` VALUES (424, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 146, 0, 1768550347);
|
||||
INSERT INTO `operation_logs` VALUES (425, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 50, 0, 1768550350);
|
||||
INSERT INTO `operation_logs` VALUES (426, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 148, 0, 1768550350);
|
||||
INSERT INTO `operation_logs` VALUES (427, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 47, 0, 1768550351);
|
||||
INSERT INTO `operation_logs` VALUES (428, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 50, 0, 1768550352);
|
||||
INSERT INTO `operation_logs` VALUES (429, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 52, 0, 1768550352);
|
||||
INSERT INTO `operation_logs` VALUES (430, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 50, 0, 1768550353);
|
||||
INSERT INTO `operation_logs` VALUES (431, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 52, 0, 1768550355);
|
||||
INSERT INTO `operation_logs` VALUES (432, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 47, 0, 1768550357);
|
||||
INSERT INTO `operation_logs` VALUES (433, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768550357);
|
||||
INSERT INTO `operation_logs` VALUES (434, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 52, 0, 1768550358);
|
||||
INSERT INTO `operation_logs` VALUES (435, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 49, 0, 1768550359);
|
||||
INSERT INTO `operation_logs` VALUES (436, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 46, 0, 1768550360);
|
||||
INSERT INTO `operation_logs` VALUES (437, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768550361);
|
||||
INSERT INTO `operation_logs` VALUES (438, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 51, 0, 1768550361);
|
||||
INSERT INTO `operation_logs` VALUES (439, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 50, 0, 1768550361);
|
||||
INSERT INTO `operation_logs` VALUES (440, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 43, 0, 1768550362);
|
||||
INSERT INTO `operation_logs` VALUES (441, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 45, 0, 1768550364);
|
||||
INSERT INTO `operation_logs` VALUES (442, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 48, 0, 1768550368);
|
||||
INSERT INTO `operation_logs` VALUES (443, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 50, 0, 1768550371);
|
||||
INSERT INTO `operation_logs` VALUES (444, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 49, 0, 1768550378);
|
||||
INSERT INTO `operation_logs` VALUES (445, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 53, 0, 1768550384);
|
||||
INSERT INTO `operation_logs` VALUES (446, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 48, 0, 1768550387);
|
||||
INSERT INTO `operation_logs` VALUES (447, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 50, 0, 1768550390);
|
||||
INSERT INTO `operation_logs` VALUES (448, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, 0, 1768550412);
|
||||
INSERT INTO `operation_logs` VALUES (449, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 49, 0, 1768550416);
|
||||
INSERT INTO `operation_logs` VALUES (450, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 48, 0, 1768550417);
|
||||
INSERT INTO `operation_logs` VALUES (451, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 49, 0, 1768550418);
|
||||
INSERT INTO `operation_logs` VALUES (452, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 49, 0, 1768550419);
|
||||
INSERT INTO `operation_logs` VALUES (453, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 49, 0, 1768550420);
|
||||
INSERT INTO `operation_logs` VALUES (454, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 50, 0, 1768550421);
|
||||
INSERT INTO `operation_logs` VALUES (455, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 48, 0, 1768550428);
|
||||
INSERT INTO `operation_logs` VALUES (456, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 45, 0, 1768550511);
|
||||
INSERT INTO `operation_logs` VALUES (457, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 44, 0, 1768550512);
|
||||
INSERT INTO `operation_logs` VALUES (458, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 44, 0, 1768550552);
|
||||
INSERT INTO `operation_logs` VALUES (459, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 44, 0, 1768550556);
|
||||
INSERT INTO `operation_logs` VALUES (460, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 46, 0, 1768550737);
|
||||
INSERT INTO `operation_logs` VALUES (461, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 53, 0, 1768550762);
|
||||
INSERT INTO `operation_logs` VALUES (462, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 51, 0, 1768550799);
|
||||
INSERT INTO `operation_logs` VALUES (463, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 49, 0, 1768550800);
|
||||
INSERT INTO `operation_logs` VALUES (464, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 46, 0, 1768550801);
|
||||
INSERT INTO `operation_logs` VALUES (465, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768550802);
|
||||
INSERT INTO `operation_logs` VALUES (466, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 45, 0, 1768550804);
|
||||
INSERT INTO `operation_logs` VALUES (467, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 47, 0, 1768550805);
|
||||
INSERT INTO `operation_logs` VALUES (468, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 48, 0, 1768550845);
|
||||
INSERT INTO `operation_logs` VALUES (469, 1, 'lq', '::1', '/api/admin/tags', 'POST', '{\"name\":\"Goravel\",\"description\":\"一个框架\"}', 200, 55, 0, 1768550858);
|
||||
INSERT INTO `operation_logs` VALUES (470, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 48, 0, 1768550858);
|
||||
INSERT INTO `operation_logs` VALUES (471, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 48, 0, 1768550860);
|
||||
INSERT INTO `operation_logs` VALUES (472, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 45, 0, 1768550862);
|
||||
INSERT INTO `operation_logs` VALUES (473, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 52, 0, 1768550873);
|
||||
INSERT INTO `operation_logs` VALUES (474, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 50, 0, 1768550882);
|
||||
INSERT INTO `operation_logs` VALUES (475, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 144, 0, 1768550891);
|
||||
INSERT INTO `operation_logs` VALUES (476, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 52, 0, 1768550894);
|
||||
INSERT INTO `operation_logs` VALUES (477, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, 0, 1768550903);
|
||||
INSERT INTO `operation_logs` VALUES (478, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 288, 0, 1768550907);
|
||||
INSERT INTO `operation_logs` VALUES (479, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 50, 0, 1768550908);
|
||||
INSERT INTO `operation_logs` VALUES (480, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 248, 0, 1768550908);
|
||||
INSERT INTO `operation_logs` VALUES (481, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 48, 0, 1768550942);
|
||||
INSERT INTO `operation_logs` VALUES (482, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 50, 0, 1768550946);
|
||||
INSERT INTO `operation_logs` VALUES (483, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 46, 0, 1768550949);
|
||||
INSERT INTO `operation_logs` VALUES (484, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 50, 0, 1768550956);
|
||||
INSERT INTO `operation_logs` VALUES (485, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 55, 0, 1768550962);
|
||||
INSERT INTO `operation_logs` VALUES (486, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 50, 0, 1768550975);
|
||||
INSERT INTO `operation_logs` VALUES (487, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768550981);
|
||||
INSERT INTO `operation_logs` VALUES (488, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 47, 0, 1768551019);
|
||||
INSERT INTO `operation_logs` VALUES (489, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 44, 0, 1768551041);
|
||||
INSERT INTO `operation_logs` VALUES (490, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 41, 0, 1768551046);
|
||||
INSERT INTO `operation_logs` VALUES (491, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 49, 0, 1768551062);
|
||||
INSERT INTO `operation_logs` VALUES (492, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 53, 0, 1768551144);
|
||||
INSERT INTO `operation_logs` VALUES (493, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 46, 0, 1768551146);
|
||||
INSERT INTO `operation_logs` VALUES (494, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 49, 0, 1768551148);
|
||||
INSERT INTO `operation_logs` VALUES (495, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 44, 0, 1768551155);
|
||||
INSERT INTO `operation_logs` VALUES (496, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 47, 0, 1768551156);
|
||||
INSERT INTO `operation_logs` VALUES (497, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 49, 0, 1768551159);
|
||||
INSERT INTO `operation_logs` VALUES (498, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 51, 0, 1768551703);
|
||||
INSERT INTO `operation_logs` VALUES (499, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 46, 0, 1768551791);
|
||||
INSERT INTO `operation_logs` VALUES (500, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 50, 0, 1768552157);
|
||||
INSERT INTO `operation_logs` VALUES (501, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 50, 0, 1768552159);
|
||||
INSERT INTO `operation_logs` VALUES (502, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 47, 0, 1768552162);
|
||||
INSERT INTO `operation_logs` VALUES (503, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 49, 0, 1768552163);
|
||||
INSERT INTO `operation_logs` VALUES (504, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 47, 0, 1768552164);
|
||||
INSERT INTO `operation_logs` VALUES (505, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 46, 0, 1768552165);
|
||||
INSERT INTO `operation_logs` VALUES (506, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 46, 0, 1768552166);
|
||||
INSERT INTO `operation_logs` VALUES (507, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 52, 0, 1768552167);
|
||||
INSERT INTO `operation_logs` VALUES (508, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 46, 0, 1768552168);
|
||||
INSERT INTO `operation_logs` VALUES (509, 1, 'lq', '::1', '/api/admin/columns', 'POST', '{\"name\":\"Goravel\",\"cover\":\"https://www.goravel.dev/logo.png\",\"description\":\"Goravel入门手册\",\"isActive\":1,\"sortOrder\":0}', 200, 50, 0, 1768552230);
|
||||
INSERT INTO `operation_logs` VALUES (510, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 54, 0, 1768552230);
|
||||
INSERT INTO `operation_logs` VALUES (511, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 50, 0, 1768552232);
|
||||
INSERT INTO `operation_logs` VALUES (512, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 306, 0, 1768552236);
|
||||
INSERT INTO `operation_logs` VALUES (513, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 52, 0, 1768552237);
|
||||
INSERT INTO `operation_logs` VALUES (514, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 247, 0, 1768552237);
|
||||
INSERT INTO `operation_logs` VALUES (515, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 49, 0, 1768552239);
|
||||
INSERT INTO `operation_logs` VALUES (516, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 138, 0, 1768552239);
|
||||
INSERT INTO `operation_logs` VALUES (517, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768552241);
|
||||
INSERT INTO `operation_logs` VALUES (518, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768552245);
|
||||
INSERT INTO `operation_logs` VALUES (519, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 45, 0, 1768552246);
|
||||
INSERT INTO `operation_logs` VALUES (520, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 51, 0, 1768552441);
|
||||
INSERT INTO `operation_logs` VALUES (521, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 44, 0, 1768552446);
|
||||
INSERT INTO `operation_logs` VALUES (522, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 46, 0, 1768552451);
|
||||
INSERT INTO `operation_logs` VALUES (523, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 45, 0, 1768552468);
|
||||
INSERT INTO `operation_logs` VALUES (524, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 49, 0, 1768552827);
|
||||
INSERT INTO `operation_logs` VALUES (525, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768552828);
|
||||
INSERT INTO `operation_logs` VALUES (526, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768552829);
|
||||
INSERT INTO `operation_logs` VALUES (527, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768552832);
|
||||
INSERT INTO `operation_logs` VALUES (528, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 49, 0, 1768552837);
|
||||
INSERT INTO `operation_logs` VALUES (529, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768552848);
|
||||
INSERT INTO `operation_logs` VALUES (530, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 51, 0, 1768552864);
|
||||
INSERT INTO `operation_logs` VALUES (531, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 48, 0, 1768552947);
|
||||
INSERT INTO `operation_logs` VALUES (532, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 49, 0, 1768553042);
|
||||
INSERT INTO `operation_logs` VALUES (533, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 44, 0, 1768553044);
|
||||
INSERT INTO `operation_logs` VALUES (534, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768553064);
|
||||
INSERT INTO `operation_logs` VALUES (535, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 48, 0, 1768553070);
|
||||
INSERT INTO `operation_logs` VALUES (536, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 45, 0, 1768553076);
|
||||
INSERT INTO `operation_logs` VALUES (537, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768553101);
|
||||
INSERT INTO `operation_logs` VALUES (538, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 51, 0, 1768553102);
|
||||
INSERT INTO `operation_logs` VALUES (539, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 54, 0, 1768553264);
|
||||
INSERT INTO `operation_logs` VALUES (540, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 46, 0, 1768553267);
|
||||
INSERT INTO `operation_logs` VALUES (541, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 48, 0, 1768553268);
|
||||
INSERT INTO `operation_logs` VALUES (542, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 47, 0, 1768553273);
|
||||
INSERT INTO `operation_logs` VALUES (543, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 49, 0, 1768553274);
|
||||
INSERT INTO `operation_logs` VALUES (544, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768553275);
|
||||
INSERT INTO `operation_logs` VALUES (545, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768553278);
|
||||
INSERT INTO `operation_logs` VALUES (546, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 48, 0, 1768553334);
|
||||
INSERT INTO `operation_logs` VALUES (547, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768553412);
|
||||
INSERT INTO `operation_logs` VALUES (548, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 50, 0, 1768553414);
|
||||
INSERT INTO `operation_logs` VALUES (549, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 48, 0, 1768553626);
|
||||
INSERT INTO `operation_logs` VALUES (550, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768553633);
|
||||
INSERT INTO `operation_logs` VALUES (551, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 50, 0, 1768553635);
|
||||
INSERT INTO `operation_logs` VALUES (552, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 145, 0, 1768553636);
|
||||
INSERT INTO `operation_logs` VALUES (553, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 46, 0, 1768553638);
|
||||
INSERT INTO `operation_logs` VALUES (554, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 48, 0, 1768553639);
|
||||
INSERT INTO `operation_logs` VALUES (555, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 45, 0, 1768553640);
|
||||
INSERT INTO `operation_logs` VALUES (556, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 51, 0, 1768553641);
|
||||
INSERT INTO `operation_logs` VALUES (557, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 48, 0, 1768553646);
|
||||
INSERT INTO `operation_logs` VALUES (558, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 46, 0, 1768553649);
|
||||
INSERT INTO `operation_logs` VALUES (559, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 49, 0, 1768553649);
|
||||
INSERT INTO `operation_logs` VALUES (560, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 47, 0, 1768553650);
|
||||
INSERT INTO `operation_logs` VALUES (561, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 50, 0, 1768553651);
|
||||
INSERT INTO `operation_logs` VALUES (562, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 53, 0, 1768553652);
|
||||
INSERT INTO `operation_logs` VALUES (563, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 51, 0, 1768553652);
|
||||
INSERT INTO `operation_logs` VALUES (564, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 292, 0, 1768553653);
|
||||
INSERT INTO `operation_logs` VALUES (565, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 53, 0, 1768553654);
|
||||
INSERT INTO `operation_logs` VALUES (566, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 259, 0, 1768553654);
|
||||
INSERT INTO `operation_logs` VALUES (567, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 50, 0, 1768553670);
|
||||
INSERT INTO `operation_logs` VALUES (568, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 48, 0, 1768553671);
|
||||
INSERT INTO `operation_logs` VALUES (569, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 48, 0, 1768553818);
|
||||
INSERT INTO `operation_logs` VALUES (570, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 50, 0, 1768553822);
|
||||
INSERT INTO `operation_logs` VALUES (571, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768553823);
|
||||
INSERT INTO `operation_logs` VALUES (572, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 45, 0, 1768553824);
|
||||
INSERT INTO `operation_logs` VALUES (573, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 43, 0, 1768554151);
|
||||
INSERT INTO `operation_logs` VALUES (574, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 48, 0, 1768554160);
|
||||
INSERT INTO `operation_logs` VALUES (575, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 53, 0, 1768800708);
|
||||
INSERT INTO `operation_logs` VALUES (576, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 236, 0, 1768800708);
|
||||
INSERT INTO `operation_logs` VALUES (577, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 46, 0, 1768800710);
|
||||
INSERT INTO `operation_logs` VALUES (578, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 223, 0, 1768800710);
|
||||
INSERT INTO `operation_logs` VALUES (579, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 46, 0, 1768800712);
|
||||
INSERT INTO `operation_logs` VALUES (580, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 46, 0, 1768800713);
|
||||
INSERT INTO `operation_logs` VALUES (581, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 46, 0, 1768800714);
|
||||
INSERT INTO `operation_logs` VALUES (582, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 49, 0, 1768800715);
|
||||
INSERT INTO `operation_logs` VALUES (583, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 46, 0, 1768800716);
|
||||
INSERT INTO `operation_logs` VALUES (584, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 43, 0, 1768800716);
|
||||
INSERT INTO `operation_logs` VALUES (585, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 43, 0, 1768800719);
|
||||
INSERT INTO `operation_logs` VALUES (586, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 48, 0, 1768800720);
|
||||
INSERT INTO `operation_logs` VALUES (587, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768800821);
|
||||
INSERT INTO `operation_logs` VALUES (588, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 50, 0, 1768800828);
|
||||
INSERT INTO `operation_logs` VALUES (589, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 139, 0, 1768800829);
|
||||
INSERT INTO `operation_logs` VALUES (590, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 47, 0, 1768800831);
|
||||
INSERT INTO `operation_logs` VALUES (591, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 49, 0, 1768800833);
|
||||
INSERT INTO `operation_logs` VALUES (592, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 46, 0, 1768800834);
|
||||
INSERT INTO `operation_logs` VALUES (593, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 46, 0, 1768801041);
|
||||
INSERT INTO `operation_logs` VALUES (594, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 44, 0, 1768801043);
|
||||
INSERT INTO `operation_logs` VALUES (595, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 44, 0, 1768801070);
|
||||
INSERT INTO `operation_logs` VALUES (596, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768801071);
|
||||
INSERT INTO `operation_logs` VALUES (597, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 43, 0, 1768801071);
|
||||
INSERT INTO `operation_logs` VALUES (598, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768801071);
|
||||
INSERT INTO `operation_logs` VALUES (599, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 43, 0, 1768801072);
|
||||
INSERT INTO `operation_logs` VALUES (600, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 43, 0, 1768801072);
|
||||
INSERT INTO `operation_logs` VALUES (601, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 46, 0, 1768801072);
|
||||
INSERT INTO `operation_logs` VALUES (602, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768801072);
|
||||
INSERT INTO `operation_logs` VALUES (603, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768801072);
|
||||
INSERT INTO `operation_logs` VALUES (604, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 46, 0, 1768801072);
|
||||
INSERT INTO `operation_logs` VALUES (605, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768801073);
|
||||
INSERT INTO `operation_logs` VALUES (606, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768801073);
|
||||
INSERT INTO `operation_logs` VALUES (607, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768801073);
|
||||
INSERT INTO `operation_logs` VALUES (608, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768801073);
|
||||
INSERT INTO `operation_logs` VALUES (609, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768801073);
|
||||
INSERT INTO `operation_logs` VALUES (610, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 51, 0, 1768801074);
|
||||
INSERT INTO `operation_logs` VALUES (611, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768801074);
|
||||
INSERT INTO `operation_logs` VALUES (612, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 46, 0, 1768801074);
|
||||
INSERT INTO `operation_logs` VALUES (613, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768801074);
|
||||
INSERT INTO `operation_logs` VALUES (614, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 44, 0, 1768801074);
|
||||
INSERT INTO `operation_logs` VALUES (615, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 43, 0, 1768801074);
|
||||
INSERT INTO `operation_logs` VALUES (616, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 43, 0, 1768801075);
|
||||
INSERT INTO `operation_logs` VALUES (617, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768801075);
|
||||
INSERT INTO `operation_logs` VALUES (618, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 49, 0, 1768801075);
|
||||
INSERT INTO `operation_logs` VALUES (619, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768801075);
|
||||
INSERT INTO `operation_logs` VALUES (620, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 47, 0, 1768801075);
|
||||
INSERT INTO `operation_logs` VALUES (621, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768801077);
|
||||
INSERT INTO `operation_logs` VALUES (622, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 45, 0, 1768801079);
|
||||
INSERT INTO `operation_logs` VALUES (623, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 48, 0, 1768801080);
|
||||
INSERT INTO `operation_logs` VALUES (624, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 44, 0, 1768801085);
|
||||
INSERT INTO `operation_logs` VALUES (625, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 50, 0, 1768801099);
|
||||
INSERT INTO `operation_logs` VALUES (626, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 92, 0, 1768801973);
|
||||
INSERT INTO `operation_logs` VALUES (627, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 268, 0, 1768801973);
|
||||
INSERT INTO `operation_logs` VALUES (628, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 362, 0, 1768801974);
|
||||
INSERT INTO `operation_logs` VALUES (629, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 137, 0, 1768801978);
|
||||
INSERT INTO `operation_logs` VALUES (630, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 46, 0, 1768801979);
|
||||
INSERT INTO `operation_logs` VALUES (631, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 46, 0, 1768801980);
|
||||
INSERT INTO `operation_logs` VALUES (632, 1, 'lq', '::1', '/api/admin/works', 'GET', '', 200, 89, 0, 1768801980);
|
||||
INSERT INTO `operation_logs` VALUES (633, 1, 'lq', '::1', '/api/admin/snippets', 'GET', '', 200, 91, 0, 1768801981);
|
||||
INSERT INTO `operation_logs` VALUES (634, 1, 'lq', '::1', '/api/admin/tags', 'GET', '', 200, 47, 0, 1768801982);
|
||||
INSERT INTO `operation_logs` VALUES (635, 1, 'lq', '::1', '/api/admin/about', 'GET', '', 200, 47, 0, 1768801983);
|
||||
INSERT INTO `operation_logs` VALUES (636, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 46, 0, 1768801986);
|
||||
INSERT INTO `operation_logs` VALUES (637, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 50, 0, 1768801987);
|
||||
INSERT INTO `operation_logs` VALUES (638, 1, 'lq', '::1', '/api/admin/inquiries', 'GET', '', 200, 44, 0, 1768801990);
|
||||
INSERT INTO `operation_logs` VALUES (639, 1, 'lq', '::1', '/api/admin/email-suffixes', 'GET', '', 200, 45, 0, 1768801991);
|
||||
INSERT INTO `operation_logs` VALUES (640, 1, 'lq', '::1', '/api/admin/users', 'GET', '', 200, 90, 0, 1768801994);
|
||||
INSERT INTO `operation_logs` VALUES (641, 1, 'lq', '::1', '/api/admin/roles', 'GET', '', 200, 136, 0, 1768801995);
|
||||
INSERT INTO `operation_logs` VALUES (642, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 49, 0, 1768801996);
|
||||
INSERT INTO `operation_logs` VALUES (643, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 91, 0, 1768801997);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for partners
|
||||
-- ----------------------------
|
||||
@@ -982,6 +338,7 @@ CREATE TABLE `posts` (
|
||||
`original_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '原始字符串ID备份',
|
||||
`title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章标题',
|
||||
`category_id` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '文章分类ID',
|
||||
`column_id` int UNSIGNED NULL DEFAULT NULL COMMENT '专栏ID(可选)',
|
||||
`excerpt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '文章摘要',
|
||||
`content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章内容',
|
||||
`read_count` int UNSIGNED NULL DEFAULT 0 COMMENT '阅读量',
|
||||
@@ -991,6 +348,7 @@ CREATE TABLE `posts` (
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_category_id`(`category_id` ASC) USING BTREE COMMENT '按分类查询索引',
|
||||
INDEX `idx_column_id`(`column_id` ASC) USING BTREE COMMENT '按专栏查询索引',
|
||||
INDEX `idx_is_published`(`is_published` ASC) USING BTREE COMMENT '按发布状态查询索引',
|
||||
INDEX `idx_original_id`(`original_id` ASC) USING BTREE,
|
||||
FULLTEXT INDEX `idx_title_content`(`title`, `content`) COMMENT '标题和内容全文索引,用于搜索'
|
||||
@@ -999,12 +357,12 @@ CREATE TABLE `posts` (
|
||||
-- ----------------------------
|
||||
-- Records of posts
|
||||
-- ----------------------------
|
||||
INSERT INTO `posts` VALUES (1, 'refactor', '重构的艺术:如何优雅地处理遗留代码', 4, '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '<h2>什么是代码重构?</h2><p>代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...</p>', 5, 1, 0, 1768291814, 1768538949);
|
||||
INSERT INTO `posts` VALUES (2, 'shader', '着色器魔法:从零开始写一个噪声生成器', 2, '深入了解WebGL着色器,学习如何从零开始实现一个高性能的噪声生成器,为你的3D作品增添独特的视觉效果。', ' <p>GLSL (OpenGL Shading Language) 是一门让人生畏但也充满魅力的语言。它运行在 GPU 上,能够并行处理数百万个像素,创造出惊人的视觉效果。</p>\r\n <h2>什么是柏林噪声?</h2>\r\n <p>柏林噪声(Perlin Noise)是一种梯度噪声,它比普通的随机数生成的噪声看起来更自然、更平滑。它常被用来模拟云彩、地形、火焰等自然现象。</p>\r\n <h2>Three.js 中的实现</h2>\r\n <p>在 Three.js 中,我们可以通过 <code>ShaderMaterial</code> 直接编写 GLSL 代码。</p>\r\n <pre><code><span class=\"code-comment\">// 简单的顶点着色器</span>\r\n<span class=\"code-keyword\">varying</span> <span class=\"code-keyword\">vec2</span> vUv;\r\n<span class=\"code-keyword\">void</span> <span class=\"code-func\">main</span>() {\r\n vUv = uv;\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r\n}</code></pre>\r\n <p>通过调整噪声的频率和振幅,我们可以得到各种不同的纹理效果。在我的个人网站背景中,就使用了这种技术来生成流动的极光效果。</p>\r\n ', 1, 1, 0, 1768291815, 1768538949);
|
||||
INSERT INTO `posts` VALUES (3, 'ux', '用户体验设计:从认知心理学到交互实践', 3, '探索用户体验设计的核心原理,结合认知心理学知识,学习如何设计出真正符合用户需求的交互界面。', '<h2>认知心理学在UX设计中的应用</h2><p>了解用户的认知过程是设计良好用户体验的基础...</p>', 0, 1, 0, 1768291816, 1768538949);
|
||||
INSERT INTO `posts` VALUES (4, 'goravel-guide-1', 'Goravel 入门指南 (一):环境搭建与 Hello World', 4, '本文将带你了解 Go 语言领域的 Laravel —— Goravel 框架,并演示如何快速搭建环境及运行第一个 Web 服务。', '## 什么是 Goravel?\n\nGoravel 是一个基于 Go 语言的 Web 开发框架,它的设计理念深受 PHP Laravel 框架的启发。如果你是一名从 PHP 转 Go 的开发者,或者你喜欢 Laravel 那种“开箱即用、优雅简洁”的开发体验,那么 Goravel 绝对是你的不二之选。\n\n它集成了丰富的功能模块,包括但不限于:\n- 强大的路由系统\n- ORM(基于 GORM 封装)\n- 依赖注入容器\n- 队列与任务调度\n- 缓存与文件存储\n\n## 环境搭建\n\nGoravel 提供了一个名为 `knit` 的命令行工具(类似 Laravel 的 artisan),可以帮助我们快速初始化项目。\n\n### 1. 安装 Knit CLI\n\n确保你已经安装了 Go (1.20+),然后运行以下命令:\n\n```bash\ngo install github.com/goravel/knit/cmd/knit@latest\n```\n\n### 2. 创建新项目\n\n使用 `knit new` 命令创建项目:\n\n```bash\nknit new my-goravel-app\ncd my-goravel-app\n```\n\n### 3. 安装依赖\n\n```bash\ngo mod tidy\n```\n\n## 目录结构\n\n打开项目,你会发现它的目录结构非常清晰,带有浓厚的 Laravel 风格:\n\n- **app/**: 核心业务代码(Http 控制器、模型、服务提供者等)\n- **config/**: 配置文件(应用配置、数据库配置等)\n- **routes/**: 路由定义文件\n- **database/**: 数据库迁移与填充\n- **public/**: 静态资源文件\n\n## 运行 Hello World\n\nGoravel 的入口文件是根目录下的 `main.go`。在运行之前,我们先看一眼路由定义。打开 `routes/web.go`:\n\n```go\npackage routes\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n \"github.com/goravel/framework/facades\"\n)\n\nfunc Web() {\n facades.Route().Get(\"/\", func(ctx http.Context) http.Response {\n return ctx.Response().Json(200, http.Json{\n \"Hello\": \"Goravel\",\n })\n })\n}\n```\n\n非常直观!现在让我们启动服务:\n\n```bash\ngo run .\n```\n\n默认情况下,服务会运行在 `http://localhost:3000`。打开浏览器访问,你应该能看到 JSON 响应:\n\n```json\n{\n \"Hello\": \"Goravel\"\n}\n```\n\n至此,你已经成功运行了你的第一个 Goravel 应用!在下一篇文章中,我们将深入探讨路由与控制器的使用。', 1210, 1, 0, 1768465932, 1768538949);
|
||||
INSERT INTO `posts` VALUES (5, 'goravel-guide-2', 'Goravel 入门指南 (二):路由与控制器', 4, '深入理解 Goravel 的 HTTP 层,学习如何定义 RESTful 路由、创建控制器以及处理 HTTP 请求与响应。', '## 路由系统\n\n在 Goravel 中,路由定义通常位于 `routes/` 目录下。`api.go` 用于定义 API 路由,`web.go` 用于定义网页路由。Goravel 使用 `facades.Route()` 来定义路由,这得益于其强大的依赖注入系统。\n\n### 基础路由\n\n```go\n// GET 请求\nfacades.Route().Get(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().String(200, \"User List\")\n})\n\n// POST 请求\nfacades.Route().Post(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().Success().Json(http.Json{\"id\": 1})\n})\n```\n\n### 路由参数\n\n获取 URL 中的动态参数非常简单:\n\n```go\nfacades.Route().Get(\"/users/{id}\", func(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n return ctx.Response().Success().Json(http.Json{\"user_id\": id})\n})\n```\n\n## 控制器 (Controllers)\n\n随着应用变大,我们不可能把所有逻辑都写在路由闭包里。这时候就需要控制器了。\n\n### 创建控制器\n\n使用 `knit` 工具可以快速生成控制器:\n\n```bash\nknit make:controller UserController\n```\n\n这会在 `app/http/controllers` 目录下生成 `user_controller.go`。让我们修改它来添加一个 `Show` 方法:\n\n```go\npackage controllers\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n)\n\ntype UserController struct {\n // 可以在这里注入服务\n}\n\nfunc NewUserController() *UserController {\n return &UserController{}\n}\n\nfunc (r *UserController) Show(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n // 模拟数据库查询\n return ctx.Response().Success().Json(http.Json{\n \"id\": id,\n \"name\": \"Goravel User\",\n })\n}\n```\n\n### 注册控制器路由\n\n回到 `routes/api.go`,我们需要先实例化控制器,然后绑定路由:\n\n```go\nimport \"my-goravel-app/app/http/controllers\"\n\nfunc Api() {\n userController := controllers.NewUserController()\n \n // 绑定到控制器方法\n facades.Route().Get(\"/users/{id}\", userController.Show)\n}\n```\n\n## 请求与响应\n\n在控制器方法中,`ctx` (http.Context) 是核心:\n\n- **获取输入**: `ctx.Request().Input(\"key\")`\n- **获取 JSON**: `ctx.Request().Bind(&user)`\n- **返回 JSON**: `ctx.Response().Json(200, data)`\n- **设置状态码**: `ctx.Response().Status(404)`\n\n通过这种方式,Goravel 让 HTTP 层的处理变得异常清晰和标准化。下一章,我们将学习如何通过 ORM 操作数据库。', 901, 1, 0, 1768465933, 1768538949);
|
||||
INSERT INTO `posts` VALUES (6, 'goravel-guide-3', 'Goravel 入门指南 (三):ORM 数据库操作', 4, '掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。', '## Goravel ORM 简介\n\nGoravel 的 ORM 基于著名的 GORM 库构建,但它进行了一层优雅的封装,使其调用方式更接近 Laravel 的 Eloquent ORM。这意味着你可以使用 Facade 模式轻松调用数据库,且支持自动迁移。\n\n## 配置数据库\n\n打开 `config/database.go` 或 `.env` 文件,配置你的 MySQL 连接信息:\n\n```env\nDB_CONNECTION=mysql\nDB_HOST=127.0.0.1\nDB_PORT=3306\nDB_DATABASE=goravel\nDB_USERNAME=root\nDB_PASSWORD=password\n```\n\n## 定义模型\n\n使用 `knit` 生成模型:\n\n```bash\nknit make:model Post\n```\n\n生成的模型文件位于 `app/models/post.go`。我们需要定义结构体字段:\n\n```go\npackage models\n\nimport (\n \"github.com/goravel/framework/database/orm\"\n)\n\ntype Post struct {\n orm.Model\n Title string `gorm:\"size:255;not null\"`\n Content string `gorm:\"type:text\"`\n UserID uint\n}\n```\n\n## 数据库迁移\n\n虽然 GORM 支持 AutoMigrate,但 Goravel 推荐使用迁移文件来管理数据库变更。\n\n```bash\nknit make:migration create_posts_table\n```\n\n编辑生成的迁移文件 (`database/migrations/xxxx_create_posts_table.go`),定义表结构,然后运行:\n\n```bash\nknit migrate\n```\n\n## CRUD 操作\n\n有了模型,我们就可以愉快地操作数据库了。所有操作都通过 `facades.Orm()` 入口。\n\n### 创建 (Create)\n\n```go\npost := models.Post{\n Title: \"My First Post\",\n Content: \"Content goes here...\",\n}\nerr := facades.Orm().Query().Create(&post)\n```\n\n### 查询 (Read)\n\n```go\nvar post models.Post\n// 根据主键查询\nfacades.Orm().Query().Find(&post, 1)\n\n// 条件查询\nvar posts []models.Post\nfacades.Orm().Query().Where(\"title\", \"My First Post\").Get(&posts)\n```\n\n### 更新 (Update)\n\n```go\nvar post models.Post\nfacades.Orm().Query().Find(&post, 1)\n\npost.Title = \"Updated Title\"\nfacades.Orm().Query().Save(&post)\n```\n\n### 删除 (Delete)\n\n```go\nvar post models.Post\nfacades.Orm().Query().Delete(&post, 1)\n```\n\nGoravel 的 ORM 让 Go 语言的数据库操作不再繁琐,极大地提高了开发效率。配合之前的路由和控制器知识,你现在已经具备了开发完整 RESTful API 的能力!', 1569, 1, 0, 1768465934, 1768538949);
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (1, 'refactor', '重构的艺术:如何优雅地处理遗留代码', 4, NULL, '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '<h2>什么是代码重构?</h2><p>代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...</p>', 5, 1, 0, 1768291814, 1768538949);
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (2, 'shader', '着色器魔法:从零开始写一个噪声生成器', 2, NULL, '深入了解WebGL着色器,学习如何从零开始实现一个高性能的噪声生成器,为你的3D作品增添独特的视觉效果。', ' <p>GLSL (OpenGL Shading Language) 是一门让人生畏但也充满魅力的语言。它运行在 GPU 上,能够并行处理数百万个像素,创造出惊人的视觉效果。</p>\r\n <h2>什么是柏林噪声?</h2>\r\n <p>柏林噪声(Perlin Noise)是一种梯度噪声,它比普通的随机数生成的噪声看起来更自然、更平滑。它常被用来模拟云彩、地形、火焰等自然现象。</p>\r\n <h2>Three.js 中的实现</h2>\r\n <p>在 Three.js 中,我们可以通过 <code>ShaderMaterial</code> 直接编写 GLSL 代码。</p>\r\n <pre><code><span class=\"code-comment\">// 简单的顶点着色器</span>\r\n<span class=\"code-keyword\">varying</span> <span class=\"code-keyword\">vec2</span> vUv;\r\n<span class=\"code-keyword\">void</span> <span class=\"code-func\">main</span>() {\r\n vUv = uv;\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r\n}</code></pre>\r\n <p>通过调整噪声的频率和振幅,我们可以得到各种不同的纹理效果。在我的个人网站背景中,就使用了这种技术来生成流动的极光效果。</p>\r\n ', 1, 1, 0, 1768291815, 1768538949);
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (3, 'ux', '用户体验设计:从认知心理学到交互实践', 3, NULL, '探索用户体验设计的核心原理,结合认知心理学知识,学习如何设计出真正符合用户需求的交互界面。', '<h2>认知心理学在UX设计中的应用</h2><p>了解用户的认知过程是设计良好用户体验的基础...</p>', 0, 1, 0, 1768291816, 1768538949);
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (4, 'goravel-guide-1', 'Goravel 入门指南 (一):环境搭建与 Hello World', 4, 1, '本文将带你了解 Go 语言领域的 Laravel —— Goravel 框架,并演示如何快速搭建环境及运行第一个 Web 服务。', '## 什么是 Goravel?\n\nGoravel 是一个基于 Go 语言的 Web 开发框架,它的设计理念深受 PHP Laravel 框架的启发。如果你是一名从 PHP 转 Go 的开发者,或者你喜欢 Laravel 那种“开箱即用、优雅简洁”的开发体验,那么 Goravel 绝对是你的不二之选。\n\n它集成了丰富的功能模块,包括但不限于:\n- 强大的路由系统\n- ORM(基于 GORM 封装)\n- 依赖注入容器\n- 队列与任务调度\n- 缓存与文件存储\n\n## 环境搭建\n\nGoravel 提供了一个名为 `knit` 的命令行工具(类似 Laravel 的 artisan),可以帮助我们快速初始化项目。\n\n### 1. 安装 Knit CLI\n\n确保你已经安装了 Go (1.20+),然后运行以下命令:\n\n```bash\ngo install github.com/goravel/knit/cmd/knit@latest\n```\n\n### 2. 创建新项目\n\n使用 `knit new` 命令创建项目:\n\n```bash\nknit new my-goravel-app\ncd my-goravel-app\n```\n\n### 3. 安装依赖\n\n```bash\ngo mod tidy\n```\n\n## 目录结构\n\n打开项目,你会发现它的目录结构非常清晰,带有浓厚的 Laravel 风格:\n\n- **app/**: 核心业务代码(Http 控制器、模型、服务提供者等)\n- **config/**: 配置文件(应用配置、数据库配置等)\n- **routes/**: 路由定义文件\n- **database/**: 数据库迁移与填充\n- **public/**: 静态资源文件\n\n## 运行 Hello World\n\nGoravel 的入口文件是根目录下的 `main.go`。在运行之前,我们先看一眼路由定义。打开 `routes/web.go`:\n\n```go\npackage routes\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n \"github.com/goravel/framework/facades\"\n)\n\nfunc Web() {\n facades.Route().Get(\"/\", func(ctx http.Context) http.Response {\n return ctx.Response().Json(200, http.Json{\n \"Hello\": \"Goravel\",\n })\n })\n}\n```\n\n非常直观!现在让我们启动服务:\n\n```bash\ngo run .\n```\n\n默认情况下,服务会运行在 `http://localhost:3000`。打开浏览器访问,你应该能看到 JSON 响应:\n\n```json\n{\n \"Hello\": \"Goravel\"\n}\n```\n\n至此,你已经成功运行了你的第一个 Goravel 应用!在下一篇文章中,我们将深入探讨路由与控制器的使用。', 1210, 1, 0, 1768465932, 1768538949);
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (5, 'goravel-guide-2', 'Goravel 入门指南 (二):路由与控制器', 4, 1, '深入理解 Goravel 的 HTTP 层,学习如何定义 RESTful 路由、创建控制器以及处理 HTTP 请求与响应。', '## 路由系统\n\n在 Goravel 中,路由定义通常位于 `routes/` 目录下。`api.go` 用于定义 API 路由,`web.go` 用于定义网页路由。Goravel 使用 `facades.Route()` 来定义路由,这得益于其强大的依赖注入系统。\n\n### 基础路由\n\n```go\n// GET 请求\nfacades.Route().Get(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().String(200, \"User List\")\n})\n\n// POST 请求\nfacades.Route().Post(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().Success().Json(http.Json{\"id\": 1})\n})\n```\n\n### 路由参数\n\n获取 URL 中的动态参数非常简单:\n\n```go\nfacades.Route().Get(\"/users/{id}\", func(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n return ctx.Response().Success().Json(http.Json{\"user_id\": id})\n})\n```\n\n## 控制器 (Controllers)\n\n随着应用变大,我们不可能把所有逻辑都写在路由闭包里。这时候就需要控制器了。\n\n### 创建控制器\n\n使用 `knit` 工具可以快速生成控制器:\n\n```bash\nknit make:controller UserController\n```\n\n这会在 `app/http/controllers` 目录下生成 `user_controller.go`。让我们修改它来添加一个 `Show` 方法:\n\n```go\npackage controllers\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n)\n\ntype UserController struct {\n // 可以在这里注入服务\n}\n\nfunc NewUserController() *UserController {\n return &UserController{}\n}\n\nfunc (r *UserController) Show(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n // 模拟数据库查询\n return ctx.Response().Success().Json(http.Json{\n \"id\": id,\n \"name\": \"Goravel User\",\n })\n}\n```\n\n### 注册控制器路由\n\n回到 `routes/api.go`,我们需要先实例化控制器,然后绑定路由:\n\n```go\nimport \"my-goravel-app/app/http/controllers\"\n\nfunc Api() {\n userController := controllers.NewUserController()\n \n // 绑定到控制器方法\n facades.Route().Get(\"/users/{id}\", userController.Show)\n}\n```\n\n## 请求与响应\n\n在控制器方法中,`ctx` (http.Context) 是核心:\n\n- **获取输入**: `ctx.Request().Input(\"key\")`\n- **获取 JSON**: `ctx.Request().Bind(&user)`\n- **返回 JSON**: `ctx.Response().Json(200, data)`\n- **设置状态码**: `ctx.Response().Status(404)`\n\n通过这种方式,Goravel 让 HTTP 层的处理变得异常清晰和标准化。下一章,我们将学习如何通过 ORM 操作数据库。', 901, 1, 0, 1768465933, 1768538949);
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (6, 'goravel-guide-3', 'Goravel 入门指南 (三):ORM 数据库操作', 4, 1, '掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。', '## Goravel ORM 简介\n\nGoravel 的 ORM 基于著名的 GORM 库构建,但它进行了一层优雅的封装,使其调用方式更接近 Laravel 的 Eloquent ORM。这意味着你可以使用 Facade 模式轻松调用数据库,且支持自动迁移。\n\n## 配置数据库\n\n打开 `config/database.go` 或 `.env` 文件,配置你的 MySQL 连接信息:\n\n```env\nDB_CONNECTION=mysql\nDB_HOST=127.0.0.1\nDB_PORT=3306\nDB_DATABASE=goravel\nDB_USERNAME=root\nDB_PASSWORD=password\n```\n\n## 定义模型\n\n使用 `knit` 生成模型:\n\n```bash\nknit make:model Post\n```\n\n生成的模型文件位于 `app/models/post.go`。我们需要定义结构体字段:\n\n```go\npackage models\n\nimport (\n \"github.com/goravel/framework/database/orm\"\n)\n\ntype Post struct {\n orm.Model\n Title string `gorm:\"size:255;not null\"`\n Content string `gorm:\"type:text\"`\n UserID uint\n}\n```\n\n## 数据库迁移\n\n虽然 GORM 支持 AutoMigrate,但 Goravel 推荐使用迁移文件来管理数据库变更。\n\n```bash\nknit make:migration create_posts_table\n```\n\n编辑生成的迁移文件 (`database/migrations/xxxx_create_posts_table.go`),定义表结构,然后运行:\n\n```bash\nknit migrate\n```\n\n## CRUD 操作\n\n有了模型,我们就可以愉快地操作数据库了。所有操作都通过 `facades.Orm()` 入口。\n\n### 创建 (Create)\n\n```go\npost := models.Post{\n Title: \"My First Post\",\n Content: \"Content goes here...\",\n}\nerr := facades.Orm().Query().Create(&post)\n```\n\n### 查询 (Read)\n\n```go\nvar post models.Post\n// 根据主键查询\nfacades.Orm().Query().Find(&post, 1)\n\n// 条件查询\nvar posts []models.Post\nfacades.Orm().Query().Where(\"title\", \"My First Post\").Get(&posts)\n```\n\n### 更新 (Update)\n\n```go\nvar post models.Post\nfacades.Orm().Query().Find(&post, 1)\n\npost.Title = \"Updated Title\"\nfacades.Orm().Query().Save(&post)\n```\n\n### 删除 (Delete)\n\n```go\nvar post models.Post\nfacades.Orm().Query().Delete(&post, 1)\n```\n\nGoravel 的 ORM 让 Go 语言的数据库操作不再繁琐,极大地提高了开发效率。配合之前的路由和控制器知识,你现在已经具备了开发完整 RESTful API 的能力!', 1569, 1, 0, 1768465934, 1768538949);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for role_permissions
|
||||
@@ -1141,6 +499,8 @@ INSERT INTO `snippets` VALUES ('1', 'React 鼠标追踪 Hook', 'import { useStat
|
||||
-- ----------------------------
|
||||
-- Table structure for tags
|
||||
-- ----------------------------
|
||||
-- Ensure table is dropped even if it has dependencies
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
DROP TABLE IF EXISTS `tags`;
|
||||
CREATE TABLE `tags` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
@@ -1158,7 +518,7 @@ CREATE TABLE `tags` (
|
||||
-- ----------------------------
|
||||
-- Records of tags
|
||||
-- ----------------------------
|
||||
INSERT INTO `tags` VALUES (1, 'Goravel', '', 0, 1768550858, 1768550858);
|
||||
INSERT IGNORE INTO `tags` (`id`, `name`, `slug`, `deleted_at`, `created_at`, `updated_at`) VALUES (1, 'Goravel', '', 0, 1768550858, 1768550858);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for testimonials
|
||||
@@ -1242,9 +602,9 @@ CREATE TABLE `users` (
|
||||
-- ----------------------------
|
||||
-- Records of users
|
||||
-- ----------------------------
|
||||
INSERT INTO `users` VALUES (1, 'lq', 'liqiworker@gmail.com', '$2a$10$yl.1yCZ2PSTsW14byyDQ3uekuEiqnC4VBr/TzR6OvMqFVRuiiHu5e', 1, 'admin', 1, 0, 1768291814, 1768538951);
|
||||
INSERT INTO `users` VALUES (2, 'editor', 'editor@example.com', '$2a$10$yl.1yCZ2PSTsW14byyDQ3uekuEiqnC4VBr/TzR6OvMqFVRuiiHu5e', 2, 'editor', 1, 0, 1768291814, 1768538951);
|
||||
INSERT INTO `users` VALUES (5, 'cs', 'cs@nailaoyun.cn', '$2a$10$yl.1yCZ2PSTsW14byyDQ3uekuEiqnC4VBr/TzR6OvMqFVRuiiHu5e', 3, 'viewer', 1, 0, 1768462115, 1768538951);
|
||||
INSERT IGNORE INTO `users` (`id`, `username`, `email`, `password_hash`, `role_id`, `role`, `is_active`, `deleted_at`, `created_at`, `updated_at`) VALUES (1, 'lq', 'liqiworker@gmail.com', '$2a$14$OqZ7svgz3S68yuuXTJWz1.0CuqI2i3WvwC1jIGBy4l0mcJW9Kqvy2', 1, 'admin', 1, 0, 1768291814, 1768538951);
|
||||
INSERT IGNORE INTO `users` (`id`, `username`, `email`, `password_hash`, `role_id`, `role`, `is_active`, `deleted_at`, `created_at`, `updated_at`) VALUES (2, 'editor', 'editor@example.com', '$2a$14$OqZ7svgz3S68yuuXTJWz1.0CuqI2i3WvwC1jIGBy4l0mcJW9Kqvy2', 2, 'editor', 1, 0, 1768291814, 1768538951);
|
||||
INSERT IGNORE INTO `users` (`id`, `username`, `email`, `password_hash`, `role_id`, `role`, `is_active`, `deleted_at`, `created_at`, `updated_at`) VALUES (5, 'cs', 'cs@nailaoyun.cn', '$2a$14$OqZ7svgz3S68yuuXTJWz1.0CuqI2i3WvwC1jIGBy4l0mcJW9Kqvy2', 3, 'viewer', 1, 0, 1768462115, 1768538951);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for work_gallery
|
||||
@@ -1324,7 +684,104 @@ CREATE TABLE `works` (
|
||||
-- ----------------------------
|
||||
-- Records of works
|
||||
-- ----------------------------
|
||||
INSERT INTO `works` VALUES ('archdaily', 'ArchDaily 网站重构', '建筑设计', '2022', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'ArchDaily 是全球最受欢迎的建筑网站之一。这次重构的目标是提升移动端体验。我们使用了 Nuxt 3 进行服务端渲染(SSR)。', 1, 0, 1768291814, 1768538952);
|
||||
INSERT INTO `works` VALUES ('nova', 'Nova 交易平台', '金融科技', '2023', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'Nova 是一个专为机构交易员设计的高频交易终端。我们面临的最大挑战是如何在处理毫秒级市场数据的同时,保持界面的流畅响应。我们采用 Web Worker 来处理繁重的数据计算,避免阻塞主线程。', 1, 0, 1768291814, 1768538952);
|
||||
INSERT IGNORE INTO `works` VALUES ('archdaily', 'ArchDaily 网站重构', '建筑设计', '2022', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'ArchDaily 是全球最受欢迎的建筑网站之一。这次重构的目标是提升移动端体验。我们使用了 Nuxt 3 进行服务端渲染(SSR)。', 1, 0, 1768291814, 1768538952);
|
||||
INSERT IGNORE INTO `works` VALUES ('nova', 'Nova 交易平台', '金融科技', '2023', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'Nova 是一个专为机构交易员设计的高频交易终端。我们面临的最大挑战是如何在处理毫秒级市场数据的同时,保持界面的流畅响应。我们采用 Web Worker 来处理繁重的数据计算,避免阻塞主线程。', 1, 0, 1768291814, 1768538952);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for search_logs
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `search_logs`;
|
||||
CREATE TABLE `search_logs` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`keyword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '搜索关键词',
|
||||
`search_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '搜索类型: category/tag/column/keyword',
|
||||
`user_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户IP地址',
|
||||
`user_location` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户归属地',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_search_type`(`search_type` ASC) USING BTREE,
|
||||
INDEX `idx_created_at`(`created_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '搜索记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of search_logs
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for attachment_categories
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `attachment_categories`;
|
||||
CREATE TABLE `attachment_categories` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '分类描述',
|
||||
`sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件分类表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of attachment_categories
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for oss_configs
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `oss_configs`;
|
||||
CREATE TABLE `oss_configs` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '配置名称',
|
||||
`storage_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '存储类型: local/qcloud/aliyun/qiniu',
|
||||
`access_key` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Access Key (AES加密)',
|
||||
`secret_key` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Secret Key (AES加密)',
|
||||
`bucket` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '存储桶名称',
|
||||
`region` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '区域',
|
||||
`domain` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '访问域名',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否启用',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_storage_type`(`storage_type` ASC) USING BTREE,
|
||||
INDEX `idx_is_active`(`is_active` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OSS配置表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of oss_configs
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for attachments
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `attachments`;
|
||||
CREATE TABLE `attachments` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`category_id` int UNSIGNED NULL DEFAULT NULL COMMENT '附件分类ID',
|
||||
`original_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始文件名',
|
||||
`stored_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '存储文件名',
|
||||
`file_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件路径',
|
||||
`file_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件访问URL',
|
||||
`file_size` bigint UNSIGNED NOT NULL DEFAULT 0 COMMENT '文件大小(字节)',
|
||||
`file_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件类型: image/video/document/other',
|
||||
`mime_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'MIME类型',
|
||||
`storage_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'local' COMMENT '存储类型: local/qcloud/aliyun/qiniu',
|
||||
`oss_config_id` int UNSIGNED NULL DEFAULT NULL COMMENT 'OSS配置ID',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_category_id`(`category_id` ASC) USING BTREE,
|
||||
INDEX `idx_storage_type`(`storage_type` ASC) USING BTREE,
|
||||
INDEX `idx_file_type`(`file_type` ASC) USING BTREE,
|
||||
INDEX `idx_created_at`(`created_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of attachments
|
||||
-- ----------------------------
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
898
server/nl_blog1.sql
Normal file
898
server/nl_blog1.sql
Normal file
@@ -0,0 +1,898 @@
|
||||
/*
|
||||
Navicat Premium Dump SQL
|
||||
|
||||
Source Server : 开发环境-本地
|
||||
Source Server Type : MySQL
|
||||
Source Server Version : 80407 (8.4.7)
|
||||
Source Host : localhost:3306
|
||||
Source Schema : nl_blog
|
||||
|
||||
Target Server Type : MySQL
|
||||
Target Server Version : 80407 (8.4.7)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/01/2026 16:13:35
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for about_profiles
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `about_profiles`;
|
||||
CREATE TABLE `about_profiles` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '',
|
||||
`location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '',
|
||||
`bio` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
||||
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '',
|
||||
`wechat` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '',
|
||||
`tech_stack` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT 'JSON string or comma separated list',
|
||||
`experiences` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT 'JSON string of experience list',
|
||||
`is_primary` tinyint(1) NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of about_profiles
|
||||
-- ----------------------------
|
||||
INSERT INTO `about_profiles` VALUES (3, '年糕崽崽', 'https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4', '中国 · 浙江杭州', '嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。从早期的 jQuery 插件开发到如今的 WebGL 3D 场景构建,我始终认为:<strong>技术是骨架,艺术是灵魂。</strong><br><br>目前,我专注于高性能 B 端应用的体验升级,以及探索生成式艺术(Generative Art)在网页中的应用。闲暇之余,我喜欢在杭州的西湖边散步,或者捣腾我的客制化机械键盘。', 'liqiworker@gmail.com', 'ngzz_0218', '[\\\"Vue 3\\\",\\\"React\\\",\\\"TypeScript\\\",\\\"Three.js\\\",\\\"Golang\\\",\\\"Tailwind CSS\\\",\\\"Rust\\\",\\\"Wails\\\"]', '[{\\\"year\\\":\\\"2024 - 至今\\\",\\\"role\\\":\\\"技术负责人\\\",\\\"company\\\":\\\"某医疗平台公司\\\"},{\\\"year\\\":\\\"2020 - 2024\\\",\\\"role\\\":\\\"PHP开发工程师\\\",\\\"company\\\":\\\"某电商公司\\\"}]', 0, 0, 0, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for access_logs
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `access_logs`;
|
||||
CREATE TABLE `access_logs` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '访问者IP地址',
|
||||
`user_agent` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '访问者浏览器信息',
|
||||
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '访问路径',
|
||||
`method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'HTTP方法',
|
||||
`status_code` int UNSIGNED NOT NULL COMMENT 'HTTP状态码',
|
||||
`response_time` int UNSIGNED NOT NULL COMMENT '响应时间(毫秒)',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_path`(`path` ASC) USING BTREE COMMENT '按访问路径查询索引',
|
||||
INDEX `idx_status_code`(`status_code` ASC) USING BTREE COMMENT '按状态码查询索引'
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '访问日志表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of access_logs
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for attachment_categories
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `attachment_categories`;
|
||||
CREATE TABLE `attachment_categories` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '分类描述',
|
||||
`sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件分类表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of attachment_categories
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for attachments
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `attachments`;
|
||||
CREATE TABLE `attachments` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`category_id` int UNSIGNED NULL DEFAULT NULL COMMENT '附件分类ID',
|
||||
`original_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始文件名',
|
||||
`stored_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '存储文件名',
|
||||
`file_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件路径',
|
||||
`file_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件访问URL',
|
||||
`file_size` bigint UNSIGNED NOT NULL DEFAULT 0 COMMENT '文件大小(字节)',
|
||||
`file_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件类型: image/video/document/other',
|
||||
`mime_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'MIME类型',
|
||||
`storage_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'local' COMMENT '存储类型: local/qcloud/aliyun/qiniu',
|
||||
`oss_config_id` int UNSIGNED NULL DEFAULT NULL COMMENT 'OSS配置ID',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_category_id`(`category_id` ASC) USING BTREE,
|
||||
INDEX `idx_storage_type`(`storage_type` ASC) USING BTREE,
|
||||
INDEX `idx_file_type`(`file_type` ASC) USING BTREE,
|
||||
INDEX `idx_created_at`(`created_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of attachments
|
||||
-- ----------------------------
|
||||
INSERT INTO `attachments` VALUES (1, NULL, 'cc_upload_xTUFrRBdm7zWyjQs693a39ea93969.jpg', 'cc_upload_xTUFrRBdm7zWyjQs693a39ea93969.jpg', 'uploads\\2026\\01\\19\\cc_upload_xTUFrRBdm7zWyjQs693a39ea93969_1768807852.jpg', '/uploads/2026/01/19/cc_upload_xTUFrRBdm7zWyjQs693a39ea93969_1768807852.jpg', 40648, 'image', 'image/jpeg', 'local', NULL, 0, 1768807852, 1768807852);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for categories
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `categories`;
|
||||
CREATE TABLE `categories` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '分类ID',
|
||||
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
|
||||
`slug` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类别名',
|
||||
`description` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '描述',
|
||||
`sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_slug`(`slug` ASC) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章分类表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of categories
|
||||
-- ----------------------------
|
||||
INSERT INTO `categories` VALUES (1, '工程化', '工程化', NULL, 0, 0, 0, 0);
|
||||
INSERT INTO `categories` VALUES (2, '图形渲染', '图形渲染', NULL, 0, 0, 0, 0);
|
||||
INSERT INTO `categories` VALUES (3, '设计思维', '设计思维', NULL, 0, 0, 0, 0);
|
||||
INSERT INTO `categories` VALUES (4, 'Go语言', 'Go语言', NULL, 0, 0, 0, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for column_posts
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `column_posts`;
|
||||
CREATE TABLE `column_posts` (
|
||||
`column_id` int UNSIGNED NOT NULL COMMENT '专栏ID',
|
||||
`post_id` int UNSIGNED NOT NULL COMMENT '文章ID',
|
||||
`sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序',
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`column_id`, `post_id`) USING BTREE,
|
||||
INDEX `idx_post_id`(`post_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '专栏文章关联表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of column_posts
|
||||
-- ----------------------------
|
||||
INSERT INTO `column_posts` VALUES (0, 1, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (0, 2, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (0, 3, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (1, 4, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (1, 5, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (1, 6, 0, 1768809352);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for columns
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `columns`;
|
||||
CREATE TABLE `columns` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '专栏ID',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '专栏名称',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '专栏描述',
|
||||
`cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '专栏封面',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
|
||||
`sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '专栏表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of columns
|
||||
-- ----------------------------
|
||||
INSERT INTO `columns` VALUES (1, 'Goravel', 'Goravel入门手册', 'https://www.goravel.dev/logo.png', 1, 0, 0, 1768552230, 1768552230);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for email_suffixes
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `email_suffixes`;
|
||||
CREATE TABLE `email_suffixes` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`suffix` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱后缀',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
|
||||
`sort_order` int NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_suffix`(`suffix` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮箱后缀配置表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of email_suffixes
|
||||
-- ----------------------------
|
||||
INSERT INTO `email_suffixes` VALUES (1, '@gmail.com', 1, 1, 0, 1768523866, 1768538954);
|
||||
INSERT INTO `email_suffixes` VALUES (2, '@163.com', 1, 2, 0, 1768523866, 1768538954);
|
||||
INSERT INTO `email_suffixes` VALUES (3, '@qq.com', 1, 3, 0, 1768523866, 1768538954);
|
||||
INSERT INTO `email_suffixes` VALUES (4, '@outlook.com', 1, 4, 0, 1768523866, 1768538954);
|
||||
INSERT INTO `email_suffixes` VALUES (5, '@foxmail.com', 1, 5, 0, 1768523866, 1768538954);
|
||||
INSERT INTO `email_suffixes` VALUES (6, '@sina.com', 1, 6, 0, 1768523866, 1768538954);
|
||||
INSERT INTO `email_suffixes` VALUES (7, '@126.com', 1, 7, 0, 1768523866, 1768538954);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for inquiries
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `inquiries`;
|
||||
CREATE TABLE `inquiries` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '姓名',
|
||||
`company` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '公司/组织',
|
||||
`contact_method` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '联系方式类型(email/wechat/phone)',
|
||||
`contact_value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '联系方式值',
|
||||
`budget` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '预算范围',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '需求描述',
|
||||
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态: 0-未读, 1-已读, 2-已联系',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '合作咨询表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of inquiries
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for operation_logs
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `operation_logs`;
|
||||
CREATE TABLE `operation_logs` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` bigint UNSIGNED NOT NULL COMMENT '操作用户ID',
|
||||
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作用户名',
|
||||
`ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作IP地址',
|
||||
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作路径',
|
||||
`method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'HTTP方法',
|
||||
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '请求参数',
|
||||
`status` int NOT NULL COMMENT '响应状态码',
|
||||
`duration` int NOT NULL COMMENT '响应时间(毫秒)',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_user_id`(`user_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 720 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of operation_logs
|
||||
-- ----------------------------
|
||||
INSERT INTO `operation_logs` VALUES (644, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 52, 0, 1768807488);
|
||||
INSERT INTO `operation_logs` VALUES (645, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 330, 0, 1768807489);
|
||||
INSERT INTO `operation_logs` VALUES (646, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 224, 0, 1768807490);
|
||||
INSERT INTO `operation_logs` VALUES (647, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 45, 0, 1768807512);
|
||||
INSERT INTO `operation_logs` VALUES (648, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 47, 0, 1768807513);
|
||||
INSERT INTO `operation_logs` VALUES (649, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 89, 0, 1768807513);
|
||||
INSERT INTO `operation_logs` VALUES (650, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 90, 0, 1768807515);
|
||||
INSERT INTO `operation_logs` VALUES (651, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 45, 0, 1768807516);
|
||||
INSERT INTO `operation_logs` VALUES (652, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 45, 0, 1768807569);
|
||||
INSERT INTO `operation_logs` VALUES (653, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 90, 0, 1768807569);
|
||||
INSERT INTO `operation_logs` VALUES (654, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 46, 0, 1768807801);
|
||||
INSERT INTO `operation_logs` VALUES (655, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 91, 0, 1768807801);
|
||||
INSERT INTO `operation_logs` VALUES (656, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 46, 0, 1768807805);
|
||||
INSERT INTO `operation_logs` VALUES (657, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 92, 0, 1768807805);
|
||||
INSERT INTO `operation_logs` VALUES (658, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 51, 0, 1768807839);
|
||||
INSERT INTO `operation_logs` VALUES (659, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 98, 0, 1768807839);
|
||||
INSERT INTO `operation_logs` VALUES (660, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 97, 0, 1768807843);
|
||||
INSERT INTO `operation_logs` VALUES (661, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 95, 0, 1768807844);
|
||||
INSERT INTO `operation_logs` VALUES (662, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 98, 0, 1768807845);
|
||||
INSERT INTO `operation_logs` VALUES (663, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 95, 0, 1768807852);
|
||||
INSERT INTO `operation_logs` VALUES (664, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 44, 0, 1768807858);
|
||||
INSERT INTO `operation_logs` VALUES (665, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 88, 0, 1768807858);
|
||||
INSERT INTO `operation_logs` VALUES (666, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 96, 0, 1768807861);
|
||||
INSERT INTO `operation_logs` VALUES (667, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 94, 0, 1768807862);
|
||||
INSERT INTO `operation_logs` VALUES (668, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 96, 0, 1768807863);
|
||||
INSERT INTO `operation_logs` VALUES (669, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 98, 0, 1768807863);
|
||||
INSERT INTO `operation_logs` VALUES (670, 1, 'lq', '::1', '/api/admin/attachments/1', 'DELETE', '', 400, 45, 0, 1768807885);
|
||||
INSERT INTO `operation_logs` VALUES (671, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 47, 0, 1768807890);
|
||||
INSERT INTO `operation_logs` VALUES (672, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 50, 0, 1768807890);
|
||||
INSERT INTO `operation_logs` VALUES (673, 1, 'lq', '::1', '/api/admin/attachments/1', 'DELETE', '', 400, 47, 0, 1768807893);
|
||||
INSERT INTO `operation_logs` VALUES (674, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 43, 0, 1768807942);
|
||||
INSERT INTO `operation_logs` VALUES (675, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 91, 0, 1768807942);
|
||||
INSERT INTO `operation_logs` VALUES (676, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 49, 0, 1768807943);
|
||||
INSERT INTO `operation_logs` VALUES (677, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 97, 0, 1768807943);
|
||||
INSERT INTO `operation_logs` VALUES (678, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 46, 0, 1768807958);
|
||||
INSERT INTO `operation_logs` VALUES (679, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 91, 0, 1768807958);
|
||||
INSERT INTO `operation_logs` VALUES (680, 1, 'lq', '::1', '/api/admin/operation-logs', 'GET', '', 200, 93, 0, 1768807958);
|
||||
INSERT INTO `operation_logs` VALUES (681, 1, 'lq', '::1', '/api/admin/dashboard/stats', 'GET', '', 200, 309, 0, 1768807958);
|
||||
INSERT INTO `operation_logs` VALUES (682, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 45, 0, 1768807962);
|
||||
INSERT INTO `operation_logs` VALUES (683, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 95, 0, 1768807962);
|
||||
INSERT INTO `operation_logs` VALUES (684, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 46, 0, 1768807968);
|
||||
INSERT INTO `operation_logs` VALUES (685, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 95, 0, 1768807968);
|
||||
INSERT INTO `operation_logs` VALUES (686, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 47, 0, 1768807994);
|
||||
INSERT INTO `operation_logs` VALUES (687, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 94, 0, 1768807994);
|
||||
INSERT INTO `operation_logs` VALUES (688, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 47, 0, 1768808006);
|
||||
INSERT INTO `operation_logs` VALUES (689, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 96, 0, 1768808006);
|
||||
INSERT INTO `operation_logs` VALUES (690, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 46, 0, 1768808082);
|
||||
INSERT INTO `operation_logs` VALUES (691, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 95, 0, 1768808082);
|
||||
INSERT INTO `operation_logs` VALUES (692, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 51, 0, 1768808127);
|
||||
INSERT INTO `operation_logs` VALUES (693, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 50, 0, 1768808127);
|
||||
INSERT INTO `operation_logs` VALUES (694, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 44, 0, 1768808155);
|
||||
INSERT INTO `operation_logs` VALUES (695, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 94, 0, 1768808155);
|
||||
INSERT INTO `operation_logs` VALUES (696, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 47, 0, 1768808167);
|
||||
INSERT INTO `operation_logs` VALUES (697, 1, 'lq', '::1', '/api/admin/oss-configs', 'GET', '', 200, 49, 0, 1768808167);
|
||||
INSERT INTO `operation_logs` VALUES (698, 1, 'lq', '::1', '/api/admin/oss-configs', 'GET', '', 200, 49, 0, 1768808492);
|
||||
INSERT INTO `operation_logs` VALUES (699, 1, 'lq', '::1', '/api/admin/settings', 'GET', '', 200, 49, 0, 1768808492);
|
||||
INSERT INTO `operation_logs` VALUES (700, 1, 'lq', '::1', '/api/admin/oss-configs', 'POST', '{\"name\":\"默认-本地\",\"storageType\":\"local\",\"accessKey\":\"1\",\"secretKey\":\"1\",\"bucket\":\"/uploads/\",\"region\":\"locallhost\",\"domain\":\"http://locallhost:3001\",\"isActive\":1,\"createdAt\":\"\",\"updatedAt\":\"\"}', 500, 1, 0, 1768808546);
|
||||
INSERT INTO `operation_logs` VALUES (701, 1, 'lq', '::1', '/api/admin/oss-configs', 'POST', '{\"name\":\"默认-本地\",\"storageType\":\"local\",\"accessKey\":\"1\",\"secretKey\":\"1\",\"bucket\":\"/uploads/\",\"region\":\"locallhost\",\"domain\":\"http://locallhost:3001\",\"isActive\":1,\"createdAt\":\"\",\"updatedAt\":\"\"}', 500, 1, 0, 1768808550);
|
||||
INSERT INTO `operation_logs` VALUES (702, 1, 'lq', '::1', '/api/admin/oss-configs', 'POST', '{\"name\":\"默认-本地\",\"storageType\":\"local\",\"accessKey\":\"123123aaa\",\"secretKey\":\"123123aaa\",\"bucket\":\"/uploads/\",\"region\":\"locallhost\",\"domain\":\"http://locallhost:3001\",\"isActive\":1,\"createdAt\":\"\",\"updatedAt\":\"\"}', 500, 1, 0, 1768808572);
|
||||
INSERT INTO `operation_logs` VALUES (703, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 50, 0, 1768809000);
|
||||
INSERT INTO `operation_logs` VALUES (704, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 48, 0, 1768809017);
|
||||
INSERT INTO `operation_logs` VALUES (705, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 230, 0, 1768809020);
|
||||
INSERT INTO `operation_logs` VALUES (706, 1, 'lq', '::1', '/api/admin/posts/6', 'PUT', '{\"title\":\"Goravel 入门指南 (三):ORM 数据库操作\",\"categoryId\":4,\"date\":\"2026-01-15\",\"excerpt\":\"掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。\",\"content\":\"\",\"isPublished\":1,\"tags\":[{\"id\":1}],\"columnId\":1}', 200, 509, 0, 1768809133);
|
||||
INSERT INTO `operation_logs` VALUES (707, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 265, 0, 1768809134);
|
||||
INSERT INTO `operation_logs` VALUES (708, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 266, 0, 1768809309);
|
||||
INSERT INTO `operation_logs` VALUES (709, 1, 'lq', '::1', '/api/admin/tags', 'POST', '{\"name\":\"入门教程\",\"slug\":\"入门教程\"}', 200, 98, 0, 1768809322);
|
||||
INSERT INTO `operation_logs` VALUES (710, 1, 'lq', '::1', '/api/admin/tags', 'POST', '{\"name\":\"Golang框架\",\"slug\":\"Golang框架\"}', 200, 89, 0, 1768809341);
|
||||
INSERT INTO `operation_logs` VALUES (711, 1, 'lq', '::1', '/api/admin/tags', 'POST', '{\"name\":\"前后端分离\",\"slug\":\"前后端分离\"}', 200, 91, 0, 1768809349);
|
||||
INSERT INTO `operation_logs` VALUES (712, 1, 'lq', '::1', '/api/admin/posts/6', 'PUT', '{\"title\":\"Goravel 入门指南 (三):ORM 数据库操作\",\"categoryId\":4,\"date\":\"2026-01-15\",\"excerpt\":\"掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。\",\"content\":\"\",\"isPublished\":1,\"tags\":[{\"id\":1},{\"id\":2},{\"id\":3},{\"id\":4}],\"columnId\":1}', 200, 521, 0, 1768809352);
|
||||
INSERT INTO `operation_logs` VALUES (713, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 289, 0, 1768809352);
|
||||
INSERT INTO `operation_logs` VALUES (714, 1, 'lq', '::1', '/api/admin/categories', 'GET', '', 200, 46, 0, 1768809361);
|
||||
INSERT INTO `operation_logs` VALUES (715, 1, 'lq', '::1', '/api/admin/columns', 'GET', '', 200, 49, 0, 1768809361);
|
||||
INSERT INTO `operation_logs` VALUES (716, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 302, 0, 1768810118);
|
||||
INSERT INTO `operation_logs` VALUES (717, 1, 'lq', '::1', '/api/admin/posts', 'GET', '', 200, 286, 0, 1768810284);
|
||||
INSERT INTO `operation_logs` VALUES (718, 1, 'lq', '::1', '/api/admin/attachment-categories', 'GET', '', 200, 45, 0, 1768810405);
|
||||
INSERT INTO `operation_logs` VALUES (719, 1, 'lq', '::1', '/api/admin/attachments', 'GET', '', 200, 89, 0, 1768810405);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for oss_configs
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `oss_configs`;
|
||||
CREATE TABLE `oss_configs` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '配置名称',
|
||||
`storage_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '存储类型: local/qcloud/aliyun/qiniu',
|
||||
`access_key` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Access Key (AES加密)',
|
||||
`secret_key` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Secret Key (AES加密)',
|
||||
`bucket` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '存储桶名称',
|
||||
`region` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '区域',
|
||||
`domain` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '访问域名',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否启用',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_storage_type`(`storage_type` ASC) USING BTREE,
|
||||
INDEX `idx_is_active`(`is_active` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OSS配置表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of oss_configs
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for partners
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `partners`;
|
||||
CREATE TABLE `partners` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '合作伙伴名称',
|
||||
`logo` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '合作伙伴Logo URL',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '合作伙伴介绍',
|
||||
`url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '合作伙伴官网链接',
|
||||
`sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序权重',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '合作伙伴表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of partners
|
||||
-- ----------------------------
|
||||
INSERT INTO `partners` VALUES (1, 'Vercel', 'https://upload.wikimedia.org/wikipedia/commons/5/5e/Vercel_logo_black.svg', '前端部署平台', 'https://vercel.com', 0, 0, 1768482993, 1768538953);
|
||||
INSERT INTO `partners` VALUES (2, 'Supabase', 'https://seeklogo.com/images/S/supabase-logo-DCC676FFE2-seeklogo.com.png', '开源 Firebase 替代方案', 'https://supabase.com', 0, 0, 1768482993, 1768538953);
|
||||
INSERT INTO `partners` VALUES (3, 'Stripe', 'https://upload.wikimedia.org/wikipedia/commons/b/ba/Stripe_Logo%2C_revised_2016.svg', '在线支付基础设施', 'https://stripe.com', 0, 0, 1768482993, 1768538953);
|
||||
INSERT INTO `partners` VALUES (4, 'Algolia', 'https://upload.wikimedia.org/wikipedia/commons/6/69/Algolia-logo.svg', '搜索即服务 API', 'https://algolia.com', 0, 0, 1768482993, 1768538953);
|
||||
INSERT INTO `partners` VALUES (5, 'Prisma', 'https://seeklogo.com/images/P/prisma-logo-3805665B69-seeklogo.com.png', '下一代 ORM', 'https://prisma.io', 0, 0, 1768482993, 1768538953);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for permissions
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `permissions`;
|
||||
CREATE TABLE `permissions` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '权限名称',
|
||||
`resource` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '资源名称',
|
||||
`action` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作名称',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `unique_resource_action`(`resource` ASC, `action` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '权限表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of permissions
|
||||
-- ----------------------------
|
||||
INSERT INTO `permissions` VALUES (1, 'Create User', 'users', 'create', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (2, 'Read User', 'users', 'read', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (3, 'Update User', 'users', 'update', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (4, 'Delete User', 'users', 'delete', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (5, 'Create Role', 'roles', 'create', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (6, 'Read Role', 'roles', 'read', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (7, 'Update Role', 'roles', 'update', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (8, 'Delete Role', 'roles', 'delete', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (9, 'Create Post', 'posts', 'create', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (10, 'Read Post', 'posts', 'read', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (11, 'Update Post', 'posts', 'update', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (12, 'Delete Post', 'posts', 'delete', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (13, 'Create Work', 'works', 'create', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (14, 'Read Work', 'works', 'read', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (15, 'Update Work', 'works', 'update', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (16, 'Delete Work', 'works', 'delete', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (17, 'Create Snippet', 'snippets', 'create', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (18, 'Read Snippet', 'snippets', 'read', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (19, 'Update Snippet', 'snippets', 'update', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (20, 'Delete Snippet', 'snippets', 'delete', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (21, 'Create Setting', 'settings', 'create', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (22, 'Read Setting', 'settings', 'read', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (23, 'Update Setting', 'settings', 'update', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (24, 'Delete Setting', 'settings', 'delete', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (25, 'Create Tag', 'tags', 'create', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (26, 'Read Tag', 'tags', 'read', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (27, 'Update Tag', 'tags', 'update', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (28, 'Delete Tag', 'tags', 'delete', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (29, 'Read Operation Log', 'operation_logs', 'read', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `permissions` VALUES (30, 'Read Dashboard', 'dashboard', 'read', 0, 1768452892, 1768538956);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for post_history
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `post_history`;
|
||||
CREATE TABLE `post_history` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '历史记录ID',
|
||||
`post_id` int UNSIGNED NOT NULL COMMENT '文章ID',
|
||||
`version` int UNSIGNED NOT NULL DEFAULT 1 COMMENT '版本号',
|
||||
`title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章标题',
|
||||
`category_id` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '文章分类ID',
|
||||
`excerpt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '文章摘要',
|
||||
`content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章内容',
|
||||
`is_published` tinyint(1) NULL DEFAULT 1 COMMENT '是否已发布',
|
||||
`modified_by` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人ID',
|
||||
`modified_at` bigint NOT NULL DEFAULT 0 COMMENT '修改时间',
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_post_id`(`post_id` ASC) USING BTREE,
|
||||
INDEX `idx_version`(`version` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章历史记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of post_history
|
||||
-- ----------------------------
|
||||
INSERT INTO `post_history` VALUES (1, 6, 1, 'Goravel 入门指南 (三):ORM 数据库操作', 4, '掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。', '', 1, 1, 1768809133, 1768809133, 0);
|
||||
INSERT INTO `post_history` VALUES (2, 6, 2, 'Goravel 入门指南 (三):ORM 数据库操作', 4, '掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。', '', 1, 1, 1768809352, 1768809352, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for post_tags
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `post_tags`;
|
||||
CREATE TABLE `post_tags` (
|
||||
`tag_id` bigint UNSIGNED NOT NULL COMMENT '关联的标签ID',
|
||||
`post_id` int UNSIGNED NOT NULL COMMENT '关联的文章ID',
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`post_id`, `tag_id`) USING BTREE,
|
||||
INDEX `idx_tag_id`(`tag_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章标签关联表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of post_tags
|
||||
-- ----------------------------
|
||||
INSERT INTO `post_tags` VALUES (1, 6, 0);
|
||||
INSERT INTO `post_tags` VALUES (2, 6, 0);
|
||||
INSERT INTO `post_tags` VALUES (3, 6, 0);
|
||||
INSERT INTO `post_tags` VALUES (4, 6, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for posts
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `posts`;
|
||||
CREATE TABLE `posts` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '文章唯一标识(自增ID)',
|
||||
`original_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '原始字符串ID备份',
|
||||
`title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章标题',
|
||||
`category_id` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '文章分类ID',
|
||||
`column_id` int UNSIGNED NULL DEFAULT NULL COMMENT '专栏ID(可选)',
|
||||
`excerpt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '文章摘要',
|
||||
`content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章内容',
|
||||
`read_count` int UNSIGNED NULL DEFAULT 0 COMMENT '阅读量',
|
||||
`is_published` tinyint(1) NULL DEFAULT 1 COMMENT '是否已发布(0:草稿,1:已发布)',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_category_id`(`category_id` ASC) USING BTREE COMMENT '按分类查询索引',
|
||||
INDEX `idx_column_id`(`column_id` ASC) USING BTREE COMMENT '按专栏查询索引',
|
||||
INDEX `idx_is_published`(`is_published` ASC) USING BTREE COMMENT '按发布状态查询索引',
|
||||
INDEX `idx_original_id`(`original_id` ASC) USING BTREE,
|
||||
FULLTEXT INDEX `idx_title_content`(`title`, `content`) COMMENT '标题和内容全文索引,用于搜索'
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '博客文章表(新结构)' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of posts
|
||||
-- ----------------------------
|
||||
INSERT INTO `posts` VALUES (1, 'refactor', '重构的艺术:如何优雅地处理遗留代码', 4, NULL, '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '<h2>什么是代码重构?</h2><p>代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...</p>', 5, 1, 0, 1768291814, 1768538949);
|
||||
INSERT INTO `posts` VALUES (2, 'shader', '着色器魔法:从零开始写一个噪声生成器', 2, NULL, '深入了解WebGL着色器,学习如何从零开始实现一个高性能的噪声生成器,为你的3D作品增添独特的视觉效果。', ' <p>GLSL (OpenGL Shading Language) 是一门让人生畏但也充满魅力的语言。它运行在 GPU 上,能够并行处理数百万个像素,创造出惊人的视觉效果。</p>\r\n <h2>什么是柏林噪声?</h2>\r\n <p>柏林噪声(Perlin Noise)是一种梯度噪声,它比普通的随机数生成的噪声看起来更自然、更平滑。它常被用来模拟云彩、地形、火焰等自然现象。</p>\r\n <h2>Three.js 中的实现</h2>\r\n <p>在 Three.js 中,我们可以通过 <code>ShaderMaterial</code> 直接编写 GLSL 代码。</p>\r\n <pre><code><span class=\"code-comment\">// 简单的顶点着色器</span>\r\n<span class=\"code-keyword\">varying</span> <span class=\"code-keyword\">vec2</span> vUv;\r\n<span class=\"code-keyword\">void</span> <span class=\"code-func\">main</span>() {\r\n vUv = uv;\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r\n}</code></pre>\r\n <p>通过调整噪声的频率和振幅,我们可以得到各种不同的纹理效果。在我的个人网站背景中,就使用了这种技术来生成流动的极光效果。</p>\r\n ', 1, 1, 0, 1768291815, 1768538949);
|
||||
INSERT INTO `posts` VALUES (3, 'ux', '用户体验设计:从认知心理学到交互实践', 3, NULL, '探索用户体验设计的核心原理,结合认知心理学知识,学习如何设计出真正符合用户需求的交互界面。', '<h2>认知心理学在UX设计中的应用</h2><p>了解用户的认知过程是设计良好用户体验的基础...</p>', 0, 1, 0, 1768291816, 1768538949);
|
||||
INSERT INTO `posts` VALUES (4, 'goravel-guide-1', 'Goravel 入门指南 (一):环境搭建与 Hello World', 4, 1, '本文将带你了解 Go 语言领域的 Laravel —— Goravel 框架,并演示如何快速搭建环境及运行第一个 Web 服务。', '## 什么是 Goravel?\n\nGoravel 是一个基于 Go 语言的 Web 开发框架,它的设计理念深受 PHP Laravel 框架的启发。如果你是一名从 PHP 转 Go 的开发者,或者你喜欢 Laravel 那种“开箱即用、优雅简洁”的开发体验,那么 Goravel 绝对是你的不二之选。\n\n它集成了丰富的功能模块,包括但不限于:\n- 强大的路由系统\n- ORM(基于 GORM 封装)\n- 依赖注入容器\n- 队列与任务调度\n- 缓存与文件存储\n\n## 环境搭建\n\nGoravel 提供了一个名为 `knit` 的命令行工具(类似 Laravel 的 artisan),可以帮助我们快速初始化项目。\n\n### 1. 安装 Knit CLI\n\n确保你已经安装了 Go (1.20+),然后运行以下命令:\n\n```bash\ngo install github.com/goravel/knit/cmd/knit@latest\n```\n\n### 2. 创建新项目\n\n使用 `knit new` 命令创建项目:\n\n```bash\nknit new my-goravel-app\ncd my-goravel-app\n```\n\n### 3. 安装依赖\n\n```bash\ngo mod tidy\n```\n\n## 目录结构\n\n打开项目,你会发现它的目录结构非常清晰,带有浓厚的 Laravel 风格:\n\n- **app/**: 核心业务代码(Http 控制器、模型、服务提供者等)\n- **config/**: 配置文件(应用配置、数据库配置等)\n- **routes/**: 路由定义文件\n- **database/**: 数据库迁移与填充\n- **public/**: 静态资源文件\n\n## 运行 Hello World\n\nGoravel 的入口文件是根目录下的 `main.go`。在运行之前,我们先看一眼路由定义。打开 `routes/web.go`:\n\n```go\npackage routes\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n \"github.com/goravel/framework/facades\"\n)\n\nfunc Web() {\n facades.Route().Get(\"/\", func(ctx http.Context) http.Response {\n return ctx.Response().Json(200, http.Json{\n \"Hello\": \"Goravel\",\n })\n })\n}\n```\n\n非常直观!现在让我们启动服务:\n\n```bash\ngo run .\n```\n\n默认情况下,服务会运行在 `http://localhost:3000`。打开浏览器访问,你应该能看到 JSON 响应:\n\n```json\n{\n \"Hello\": \"Goravel\"\n}\n```\n\n至此,你已经成功运行了你的第一个 Goravel 应用!在下一篇文章中,我们将深入探讨路由与控制器的使用。', 1210, 1, 0, 1768465932, 1768538949);
|
||||
INSERT INTO `posts` VALUES (5, 'goravel-guide-2', 'Goravel 入门指南 (二):路由与控制器', 4, 1, '深入理解 Goravel 的 HTTP 层,学习如何定义 RESTful 路由、创建控制器以及处理 HTTP 请求与响应。', '## 路由系统\n\n在 Goravel 中,路由定义通常位于 `routes/` 目录下。`api.go` 用于定义 API 路由,`web.go` 用于定义网页路由。Goravel 使用 `facades.Route()` 来定义路由,这得益于其强大的依赖注入系统。\n\n### 基础路由\n\n```go\n// GET 请求\nfacades.Route().Get(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().String(200, \"User List\")\n})\n\n// POST 请求\nfacades.Route().Post(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().Success().Json(http.Json{\"id\": 1})\n})\n```\n\n### 路由参数\n\n获取 URL 中的动态参数非常简单:\n\n```go\nfacades.Route().Get(\"/users/{id}\", func(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n return ctx.Response().Success().Json(http.Json{\"user_id\": id})\n})\n```\n\n## 控制器 (Controllers)\n\n随着应用变大,我们不可能把所有逻辑都写在路由闭包里。这时候就需要控制器了。\n\n### 创建控制器\n\n使用 `knit` 工具可以快速生成控制器:\n\n```bash\nknit make:controller UserController\n```\n\n这会在 `app/http/controllers` 目录下生成 `user_controller.go`。让我们修改它来添加一个 `Show` 方法:\n\n```go\npackage controllers\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n)\n\ntype UserController struct {\n // 可以在这里注入服务\n}\n\nfunc NewUserController() *UserController {\n return &UserController{}\n}\n\nfunc (r *UserController) Show(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n // 模拟数据库查询\n return ctx.Response().Success().Json(http.Json{\n \"id\": id,\n \"name\": \"Goravel User\",\n })\n}\n```\n\n### 注册控制器路由\n\n回到 `routes/api.go`,我们需要先实例化控制器,然后绑定路由:\n\n```go\nimport \"my-goravel-app/app/http/controllers\"\n\nfunc Api() {\n userController := controllers.NewUserController()\n \n // 绑定到控制器方法\n facades.Route().Get(\"/users/{id}\", userController.Show)\n}\n```\n\n## 请求与响应\n\n在控制器方法中,`ctx` (http.Context) 是核心:\n\n- **获取输入**: `ctx.Request().Input(\"key\")`\n- **获取 JSON**: `ctx.Request().Bind(&user)`\n- **返回 JSON**: `ctx.Response().Json(200, data)`\n- **设置状态码**: `ctx.Response().Status(404)`\n\n通过这种方式,Goravel 让 HTTP 层的处理变得异常清晰和标准化。下一章,我们将学习如何通过 ORM 操作数据库。', 903, 1, 0, 1768465933, 1768538949);
|
||||
INSERT INTO `posts` VALUES (6, 'goravel-guide-3', 'Goravel 入门指南 (三):ORM 数据库操作', 4, 1, '掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。', '## Goravel ORM 简介\r\nGoravel 的 ORM 基于著名的 GORM 库构建,但它进行了一层优雅的封装,使其调用方式更接近 Laravel 的 Eloquent ORM。这意味着你可以使用 Facade 模式轻松调用数据库,且支持自动迁移。\r\n## 配置数据库\r\n打开 `config/database.go` 或 `.env` 文件,配置你的 MySQL 连接信息:\r\n```env\r\nDB_CONNECTION=mysql\r\nDB_HOST=127.0.0.1\r\nDB_PORT=3306\r\nDB_DATABASE=goravel\r\nDB_USERNAME=root\r\nDB_PASSWORD=password\r\n```\r\n## 定义模型\r\n使用 `knit` 生成模型:\r\n```bash\r\nknit make:model Post\r\n```\r\n生成的模型文件位于 `app/models/post.go`。我们需要定义结构体字段:\r\n```go\r\npackage models\r\nimport (\r\n \\\"github.com/goravel/framework/database/orm\\\"\r\n )\r\ntype Post struct {\r\n orm.Model\r\n Title string `gorm:\\\"size:255;not null\\\"`\r\n Content string `gorm:\\\"type:text\\\"`\r\n UserID uint\r\n }\r\n ```\r\n## 数据库迁移\r\n虽然 GORM 支持 AutoMigrate,但 Goravel 推荐使用迁移文件来管理数据库变更。\r\n```bash\r\nknit make:migration create_posts_table\r\n```\r\n编辑生成的迁移文件 (`database/migrations/xxxx_create_posts_table.go`),定义表结构,然后运行:\r\n```bash\r\nknit migrate\r\n```\r\n## CRUD 操作\r\n有了模型,我们就可以愉快地操作数据库了。所有操作都通过 `facades.Orm()` 入口。\r\n### 创建 (Create)\r\n```go\r\npost := models.Post{\r\n Title: \\\"My First Post\\\",\r\n Content: \\\"Content goes here...\\\",\r\n }\r\n err := facades.Orm().Query().Create(&post)\r\n ```\r\n### 查询 (Read)\r\n```go\r\nvar post models.Post\r\n// 根据主键查询\r\nfacades.Orm().Query().Find(&post, 1)\r\n// 条件查询\r\nvar posts []models.Post\r\nfacades.Orm().Query().Where(\\\"title\\\", \\\"My First Post\\\").Get(&posts)\r\n```\r\n### 更新 (Update)\r\n```go\r\nvar post models.Post\r\nfacades.Orm().Query().Find(&post, 1)\r\npost.Title = \\\"Updated Title\\\"\r\nfacades.Orm().Query().Save(&post)\r\n```\r\n### 删除 (Delete)\r\n```go\r\nvar post models.Post\r\nfacades.Orm().Query().Delete(&post, 1)\r\n```\r\nGoravel 的 ORM 让 Go 语言的数据库操作不再繁琐,极大地提高了开发效率。配合之前的路由和控制器知识,你现在已经具备了开发完整 RESTful API 的能力!', 1586, 1, 0, 1768465934, 1768809351);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for role_permissions
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `role_permissions`;
|
||||
CREATE TABLE `role_permissions` (
|
||||
`role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID',
|
||||
`permission_id` bigint UNSIGNED NOT NULL COMMENT '权限ID',
|
||||
PRIMARY KEY (`role_id`, `permission_id`) USING BTREE,
|
||||
INDEX `role_permissions_ibfk_2`(`permission_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色权限关联表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of role_permissions
|
||||
-- ----------------------------
|
||||
INSERT INTO `role_permissions` VALUES (1, 1);
|
||||
INSERT INTO `role_permissions` VALUES (1, 2);
|
||||
INSERT INTO `role_permissions` VALUES (3, 2);
|
||||
INSERT INTO `role_permissions` VALUES (1, 3);
|
||||
INSERT INTO `role_permissions` VALUES (1, 4);
|
||||
INSERT INTO `role_permissions` VALUES (1, 5);
|
||||
INSERT INTO `role_permissions` VALUES (1, 6);
|
||||
INSERT INTO `role_permissions` VALUES (3, 6);
|
||||
INSERT INTO `role_permissions` VALUES (1, 7);
|
||||
INSERT INTO `role_permissions` VALUES (1, 8);
|
||||
INSERT INTO `role_permissions` VALUES (1, 9);
|
||||
INSERT INTO `role_permissions` VALUES (2, 9);
|
||||
INSERT INTO `role_permissions` VALUES (1, 10);
|
||||
INSERT INTO `role_permissions` VALUES (2, 10);
|
||||
INSERT INTO `role_permissions` VALUES (3, 10);
|
||||
INSERT INTO `role_permissions` VALUES (1, 11);
|
||||
INSERT INTO `role_permissions` VALUES (2, 11);
|
||||
INSERT INTO `role_permissions` VALUES (1, 12);
|
||||
INSERT INTO `role_permissions` VALUES (2, 12);
|
||||
INSERT INTO `role_permissions` VALUES (1, 13);
|
||||
INSERT INTO `role_permissions` VALUES (1, 14);
|
||||
INSERT INTO `role_permissions` VALUES (3, 14);
|
||||
INSERT INTO `role_permissions` VALUES (1, 15);
|
||||
INSERT INTO `role_permissions` VALUES (1, 16);
|
||||
INSERT INTO `role_permissions` VALUES (1, 17);
|
||||
INSERT INTO `role_permissions` VALUES (1, 18);
|
||||
INSERT INTO `role_permissions` VALUES (3, 18);
|
||||
INSERT INTO `role_permissions` VALUES (1, 19);
|
||||
INSERT INTO `role_permissions` VALUES (1, 20);
|
||||
INSERT INTO `role_permissions` VALUES (1, 21);
|
||||
INSERT INTO `role_permissions` VALUES (1, 22);
|
||||
INSERT INTO `role_permissions` VALUES (3, 22);
|
||||
INSERT INTO `role_permissions` VALUES (1, 23);
|
||||
INSERT INTO `role_permissions` VALUES (1, 24);
|
||||
INSERT INTO `role_permissions` VALUES (1, 25);
|
||||
INSERT INTO `role_permissions` VALUES (1, 26);
|
||||
INSERT INTO `role_permissions` VALUES (3, 26);
|
||||
INSERT INTO `role_permissions` VALUES (1, 27);
|
||||
INSERT INTO `role_permissions` VALUES (1, 28);
|
||||
INSERT INTO `role_permissions` VALUES (1, 29);
|
||||
INSERT INTO `role_permissions` VALUES (3, 29);
|
||||
INSERT INTO `role_permissions` VALUES (1, 30);
|
||||
INSERT INTO `role_permissions` VALUES (3, 30);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for roles
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `roles`;
|
||||
CREATE TABLE `roles` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称',
|
||||
`description` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '角色描述',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `name`(`name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of roles
|
||||
-- ----------------------------
|
||||
INSERT INTO `roles` VALUES (1, 'admin', '系统管理员', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `roles` VALUES (2, 'editor', '内容编辑', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `roles` VALUES (3, 'viewer', '普通访客', 0, 1768452892, 1768538956);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for search_logs
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `search_logs`;
|
||||
CREATE TABLE `search_logs` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`keyword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '搜索关键词',
|
||||
`search_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '搜索类型: category/tag/column/keyword',
|
||||
`user_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户IP地址',
|
||||
`user_location` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户归属地',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_search_type`(`search_type` ASC) USING BTREE,
|
||||
INDEX `idx_created_at`(`created_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '搜索记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of search_logs
|
||||
-- ----------------------------
|
||||
INSERT INTO `search_logs` VALUES (1, '4', 'category', '::1', 'Internal', 0, 1768808440);
|
||||
INSERT INTO `search_logs` VALUES (2, '4', 'category', '::1', 'Internal', 0, 1768808446);
|
||||
INSERT INTO `search_logs` VALUES (3, '啊', 'keyword', '::1', 'Internal', 0, 1768808446);
|
||||
INSERT INTO `search_logs` VALUES (4, '4', 'category', '::1', 'Internal', 0, 1768808447);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for settings
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `settings`;
|
||||
CREATE TABLE `settings` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`key_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '配置项键名',
|
||||
`value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '配置项值',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '配置项描述',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `key_name`(`key_name` ASC) USING BTREE,
|
||||
INDEX `idx_key_name`(`key_name` ASC) USING BTREE COMMENT '按键名查询索引'
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '网站配置表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of settings
|
||||
-- ----------------------------
|
||||
INSERT INTO `settings` VALUES (1, 'site_title', '年糕博客', '网站标题', 0, 1768291814, 1768538952);
|
||||
INSERT INTO `settings` VALUES (2, 'site_description', '分享前端技术、交互设计以及数字艺术的深度思考', '网站描述', 0, 1768291814, 1768538952);
|
||||
INSERT INTO `settings` VALUES (3, 'site_author', '年糕崽崽', '网站作者', 0, 1768291814, 1768538952);
|
||||
INSERT INTO `settings` VALUES (4, 'site_keywords', '前端, 设计, 技术博客', '网站关键词', 0, 1768291814, 1768538952);
|
||||
INSERT INTO `settings` VALUES (5, 'posts_per_page', '10', '每页显示的文章数量', 0, 1768291814, 1768538952);
|
||||
INSERT INTO `settings` VALUES (6, 'works_per_page', '6', '每页显示的作品数量', 0, 1768291814, 1768538952);
|
||||
INSERT INTO `settings` VALUES (7, 'snippets_per_page', '8', '每页显示的代码片段数量', 0, 1768291814, 1768538952);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for snippets
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `snippets`;
|
||||
CREATE TABLE `snippets` (
|
||||
`id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码片段唯一标识',
|
||||
`title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码片段标题',
|
||||
`code` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码内容',
|
||||
`type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '代码类型(如:javascript、css、html等)',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '代码片段描述',
|
||||
`view_count` int UNSIGNED NULL DEFAULT 0 COMMENT '查看次数',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_type`(`type` ASC) USING BTREE COMMENT '按代码类型查询索引',
|
||||
INDEX `idx_view_count`(`view_count` ASC) USING BTREE COMMENT '按查看次数查询索引'
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码片段表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of snippets
|
||||
-- ----------------------------
|
||||
INSERT INTO `snippets` VALUES ('1', 'React 鼠标追踪 Hook', 'import { useState, useEffect } from \'react\';\r\n\r\nexport const useMousePosition = () => {\r\n const [pos, setPos] = useState({ x: 0, y: 0 });\r\n useEffect(() => {\r\n const update = (e) => setPos({ x: e.clientX, y: e.clientY });\r\n window.addEventListener(\'mousemove\', update);\r\n return () => window.removeEventListener(\'mousemove\', update);\r\n }, []);\r\n return pos;\r\n};', 'mouse', '这是一个鼠标追踪', 2, 0, 1768350739, 1768538952);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for tags
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `tags`;
|
||||
CREATE TABLE `tags` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标签名称',
|
||||
`slug` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标签别名,用于URL',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `name`(`name` ASC) USING BTREE,
|
||||
UNIQUE INDEX `slug`(`slug` ASC) USING BTREE,
|
||||
INDEX `idx_slug`(`slug` ASC) USING BTREE COMMENT '按别名查询索引'
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '标签表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of tags
|
||||
-- ----------------------------
|
||||
INSERT INTO `tags` VALUES (1, 'Goravel', '', 0, 1768550858, 1768550858);
|
||||
INSERT INTO `tags` VALUES (2, '入门教程', '入门教程', 0, 1768809322, 1768809322);
|
||||
INSERT INTO `tags` VALUES (3, 'Golang框架', 'Golang框架', 0, 1768809341, 1768809341);
|
||||
INSERT INTO `tags` VALUES (4, '前后端分离', '前后端分离', 0, 1768809349, 1768809349);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for testimonials
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `testimonials`;
|
||||
CREATE TABLE `testimonials` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户姓名',
|
||||
`role` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '客户职位',
|
||||
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '评价内容',
|
||||
`avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '客户头像URL',
|
||||
`rating` tinyint UNSIGNED NULL DEFAULT 5 COMMENT '评分(1-5)',
|
||||
`sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序权重',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客户评价表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of testimonials
|
||||
-- ----------------------------
|
||||
INSERT INTO `testimonials` VALUES (1, 'Alex Chen', 'Product Owner @ TechFlow', '年糕不仅技术过硬,对设计细节的把控更是令人惊叹。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Alex', 5, 0, 0, 1768482993, 1768538954);
|
||||
INSERT INTO `testimonials` VALUES (2, 'Sarah Wu', 'Design Director @ ArtSpace', '很少见到能把代码写得像诗一样的工程师,合作非常愉快!', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah', 5, 0, 0, 1768482993, 1768538954);
|
||||
INSERT INTO `testimonials` VALUES (3, 'Mike Zhang', 'CTO @ FutureWave', '交付质量远超预期,特别是在性能优化方面做得非常出色。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Mike', 5, 0, 0, 1768482993, 1768538954);
|
||||
INSERT INTO `testimonials` VALUES (4, 'Jessica Li', 'Founder @ ZenMode', '从交互动效到整体架构,都体现了极高的专业水准。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=Jessica', 5, 0, 0, 1768482993, 1768538954);
|
||||
INSERT INTO `testimonials` VALUES (5, 'David Wang', 'Tech Lead @ Innovate', '代码结构清晰,注释完善,后续维护非常轻松。', 'https://api.dicebear.com/7.x/avataaars/svg?seed=David', 5, 0, 0, 1768482993, 1768538954);
|
||||
INSERT INTO `testimonials` VALUES (6, '', 'CTO', '服务很贴心,技术够硬', 'https://api.dicebear.com/7.x/avataaars/svg?seed=David', 5, 0, 0, 20260116130552, 20260116130552);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_access_logs
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `user_access_logs`;
|
||||
CREATE TABLE `user_access_logs` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int NULL DEFAULT 0 COMMENT '用户ID(未登录用户为0)',
|
||||
`user_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户IP地址',
|
||||
`user_location` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户归属地',
|
||||
`article_id` int NOT NULL COMMENT '访问的文章ID',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`access_time` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
|
||||
INDEX `idx_article_id`(`article_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 41 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户访问记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of user_access_logs
|
||||
-- ----------------------------
|
||||
INSERT INTO `user_access_logs` VALUES (13, 0, '::1', 'Unknown', 6, 0, 20260116160835);
|
||||
INSERT INTO `user_access_logs` VALUES (14, 0, '::1', 'Unknown', 6, 0, 20260116165800);
|
||||
INSERT INTO `user_access_logs` VALUES (15, 0, '::1', 'Unknown', 6, 0, 20260119133107);
|
||||
INSERT INTO `user_access_logs` VALUES (16, 0, '::1', 'Unknown', 6, 0, 20260119133147);
|
||||
INSERT INTO `user_access_logs` VALUES (17, 0, '::1', 'Unknown', 6, 0, 20260119133216);
|
||||
INSERT INTO `user_access_logs` VALUES (18, 0, '::1', 'Internal', 6, 0, 1768801962);
|
||||
INSERT INTO `user_access_logs` VALUES (19, 1, '::1', 'Internal', 0, 0, 1768807318);
|
||||
INSERT INTO `user_access_logs` VALUES (20, 1, '::1', 'Internal', 0, 0, 1768807357);
|
||||
INSERT INTO `user_access_logs` VALUES (21, 0, '::1', 'Internal', 6, 0, 1768807466);
|
||||
INSERT INTO `user_access_logs` VALUES (22, 1, '::1', 'Internal', 0, 0, 1768807488);
|
||||
INSERT INTO `user_access_logs` VALUES (23, 0, '::1', 'Internal', 6, 0, 1768807765);
|
||||
INSERT INTO `user_access_logs` VALUES (24, 0, '::1', 'Internal', 6, 0, 1768807777);
|
||||
INSERT INTO `user_access_logs` VALUES (25, 0, '::1', 'Internal', 6, 0, 1768807795);
|
||||
INSERT INTO `user_access_logs` VALUES (26, 0, '::1', 'Internal', 6, 0, 1768809365);
|
||||
INSERT INTO `user_access_logs` VALUES (27, 0, '::1', 'Internal', 6, 0, 1768809378);
|
||||
INSERT INTO `user_access_logs` VALUES (28, 0, '::1', 'Internal', 6, 0, 1768809388);
|
||||
INSERT INTO `user_access_logs` VALUES (29, 0, '::1', 'Internal', 6, 0, 1768809694);
|
||||
INSERT INTO `user_access_logs` VALUES (30, 0, '::1', 'Internal', 6, 0, 1768809805);
|
||||
INSERT INTO `user_access_logs` VALUES (31, 0, '::1', 'Internal', 5, 0, 1768809830);
|
||||
INSERT INTO `user_access_logs` VALUES (32, 0, '::1', 'Internal', 6, 0, 1768809866);
|
||||
INSERT INTO `user_access_logs` VALUES (33, 0, '::1', 'Internal', 6, 0, 1768809889);
|
||||
INSERT INTO `user_access_logs` VALUES (34, 0, '::1', 'Internal', 6, 0, 1768809926);
|
||||
INSERT INTO `user_access_logs` VALUES (35, 0, '::1', 'Internal', 5, 0, 1768809977);
|
||||
INSERT INTO `user_access_logs` VALUES (36, 0, '::1', 'Internal', 6, 0, 1768810062);
|
||||
INSERT INTO `user_access_logs` VALUES (37, 0, '::1', 'Internal', 6, 0, 1768810115);
|
||||
INSERT INTO `user_access_logs` VALUES (38, 0, '::1', 'Internal', 6, 0, 1768810120);
|
||||
INSERT INTO `user_access_logs` VALUES (39, 0, '::1', 'Internal', 6, 0, 1768810276);
|
||||
INSERT INTO `user_access_logs` VALUES (40, 0, '::1', 'Internal', 6, 0, 1768810279);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for users
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
CREATE TABLE `users` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',
|
||||
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱地址',
|
||||
`password_hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码哈希值',
|
||||
`role_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '角色ID',
|
||||
`role` enum('admin','editor','viewer') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'viewer' COMMENT '用户角色(兼容旧版)',
|
||||
`is_active` tinyint(1) NULL DEFAULT 1 COMMENT '是否激活(0:禁用,1:激活)',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `username`(`username` ASC) USING BTREE,
|
||||
UNIQUE INDEX `email`(`email` ASC) USING BTREE,
|
||||
INDEX `idx_username`(`username` ASC) USING BTREE COMMENT '按用户名查询索引',
|
||||
INDEX `idx_email`(`email` ASC) USING BTREE COMMENT '按邮箱查询索引',
|
||||
INDEX `idx_role`(`role` ASC) USING BTREE COMMENT '按角色查询索引',
|
||||
INDEX `users_ibfk_1`(`role_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of users
|
||||
-- ----------------------------
|
||||
INSERT INTO `users` VALUES (1, 'lq', 'liqiworker@gmail.com', '$2a$10$Bq6rv7714W3jGyXruYx4puXjflMTSkq2QM9kF54x9iZnk.0AD4B1G', 1, 'admin', 1, 0, 1768291814, 1768538951);
|
||||
INSERT INTO `users` VALUES (2, 'editor', 'editor@example.com', '$2a$14$OqZ7svgz3S68yuuXTJWz1.0CuqI2i3WvwC1jIGBy4l0mcJW9Kqvy2', 2, 'editor', 1, 0, 1768291814, 1768538951);
|
||||
INSERT INTO `users` VALUES (5, 'cs', 'cs@nailaoyun.cn', '$2a$14$OqZ7svgz3S68yuuXTJWz1.0CuqI2i3WvwC1jIGBy4l0mcJW9Kqvy2', 3, 'viewer', 1, 0, 1768462115, 1768538951);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for work_gallery
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `work_gallery`;
|
||||
CREATE TABLE `work_gallery` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`work_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联的作品ID',
|
||||
`image_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '图片URL',
|
||||
`sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序顺序,数值越小越靠前',
|
||||
`description` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '图片描述',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_work_id`(`work_id` ASC) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品图库表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of work_gallery
|
||||
-- ----------------------------
|
||||
INSERT INTO `work_gallery` VALUES (1, 'nova', 'https://images.unsplash.com/photo-1642543492481-44e81e3914a7?q=80&w=2070', 1, 'Nova 交易平台首页', 0, 1768291937);
|
||||
INSERT INTO `work_gallery` VALUES (2, 'nova', 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?q=80&w=2070', 2, 'Nova 交易平台交易界面', 0, 1768291937);
|
||||
INSERT INTO `work_gallery` VALUES (3, 'archdaily', 'https://images.unsplash.com/photo-1503387762-592deb58ef4e?q=80&w=2089', 1, 'ArchDaily 网站首页', 0, 1768291937);
|
||||
INSERT INTO `work_gallery` VALUES (4, 'archdaily', 'https://images.unsplash.com/photo-1518005020951-ecc859466abc?q=80&w=1920', 2, 'ArchDaily 文章详情页', 0, 1768291937);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for work_tech_stack
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `work_tech_stack`;
|
||||
CREATE TABLE `work_tech_stack` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`work_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联的作品ID',
|
||||
`category` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '技术分类(如:前端、后端、数据库等)',
|
||||
`item` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '具体技术项(如:Vue 3、Golang、MySQL等)',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_work_id`(`work_id` ASC) USING BTREE,
|
||||
INDEX `idx_category`(`category` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品技术栈表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of work_tech_stack
|
||||
-- ----------------------------
|
||||
INSERT INTO `work_tech_stack` VALUES (1, 'nova', '前端层', 'React 18', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (2, 'nova', '前端层', 'TypeScript', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (3, 'nova', '前端层', 'D3.js', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (4, 'nova', '后端服务', 'Golang', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (5, 'nova', '后端服务', 'gRPC', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (6, 'archdaily', '核心前端', 'Vue 3', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (7, 'archdaily', '核心前端', 'Nuxt.js', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (8, 'archdaily', '核心前端', 'GSAP', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (9, 'archdaily', 'CMS', 'Strapi', 0, 1768291937);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for works
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `works`;
|
||||
CREATE TABLE `works` (
|
||||
`id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '作品唯一标识',
|
||||
`title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '作品标题',
|
||||
`category` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '作品分类',
|
||||
`year` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '创作年份',
|
||||
`hero_img` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '作品主图URL',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '作品详细描述',
|
||||
`is_featured` tinyint(1) NULL DEFAULT 0 COMMENT '是否为精选作品(0:否,1:是)',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_category`(`category` ASC) USING BTREE COMMENT '按分类查询索引',
|
||||
INDEX `idx_year`(`year` ASC) USING BTREE COMMENT '按年份查询索引',
|
||||
INDEX `idx_is_featured`(`is_featured` ASC) USING BTREE COMMENT '按精选状态查询索引'
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of works
|
||||
-- ----------------------------
|
||||
INSERT INTO `works` VALUES ('archdaily', 'ArchDaily 网站重构', '建筑设计', '2022', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'ArchDaily 是全球最受欢迎的建筑网站之一。这次重构的目标是提升移动端体验。我们使用了 Nuxt 3 进行服务端渲染(SSR)。', 1, 0, 1768291814, 1768538952);
|
||||
INSERT INTO `works` VALUES ('nova', 'Nova 交易平台', '金融科技', '2023', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'Nova 是一个专为机构交易员设计的高频交易终端。我们面临的最大挑战是如何在处理毫秒级市场数据的同时,保持界面的流畅响应。我们采用 Web Worker 来处理繁重的数据计算,避免阻塞主线程。', 1, 0, 1768291814, 1768538952);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
249
server/repositories/attachment_repository.go
Normal file
249
server/repositories/attachment_repository.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CreateAttachment 创建附件记录
|
||||
func CreateAttachment(attachment *models.Attachment) error {
|
||||
err := config.DB.Create(attachment).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating attachment: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAttachments 获取附件列表(分页)
|
||||
func GetAttachments(page, pageSize int, categoryID *uint, fileType string) ([]models.Attachment, int64, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var attachments []models.Attachment
|
||||
var total int64
|
||||
|
||||
query := config.DB.Model(&models.Attachment{}).
|
||||
Preload("Category").
|
||||
Where("deleted_at = ?", 0)
|
||||
|
||||
if categoryID != nil && *categoryID > 0 {
|
||||
query = query.Where("category_id = ?", *categoryID)
|
||||
}
|
||||
|
||||
if fileType != "" {
|
||||
query = query.Where("file_type = ?", fileType)
|
||||
}
|
||||
|
||||
// Count total
|
||||
err := query.Count(&total).Error
|
||||
if err != nil {
|
||||
log.Printf("Error counting attachments: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Get attachments
|
||||
err = query.Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&attachments).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error querying attachments: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return attachments, total, nil
|
||||
}
|
||||
|
||||
// GetAttachmentByID 根据ID获取附件
|
||||
func GetAttachmentByID(id uint) (*models.Attachment, error) {
|
||||
var attachment models.Attachment
|
||||
err := config.DB.Model(&models.Attachment{}).
|
||||
Preload("Category").
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
First(&attachment).Error
|
||||
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error getting attachment by ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &attachment, nil
|
||||
}
|
||||
|
||||
// DeleteAttachment 删除附件(软删除)
|
||||
func DeleteAttachment(id uint) error {
|
||||
err := config.DB.Model(&models.Attachment{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting attachment: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateAttachmentCategory 创建附件分类
|
||||
func CreateAttachmentCategory(category *models.AttachmentCategory) error {
|
||||
err := config.DB.Create(category).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating attachment category: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAttachmentCategories 获取所有附件分类
|
||||
func GetAttachmentCategories() ([]models.AttachmentCategory, error) {
|
||||
var categories []models.AttachmentCategory
|
||||
err := config.DB.Model(&models.AttachmentCategory{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("sort_order ASC, created_at DESC").
|
||||
Find(&categories).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error querying attachment categories: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return categories, nil
|
||||
}
|
||||
|
||||
// UpdateAttachmentCategory 更新附件分类
|
||||
func UpdateAttachmentCategory(category *models.AttachmentCategory) error {
|
||||
err := config.DB.Model(&models.AttachmentCategory{}).
|
||||
Where("id = ? AND deleted_at = ?", category.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"name": category.Name,
|
||||
"description": category.Description,
|
||||
"sort_order": category.SortOrder,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error updating attachment category: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAttachmentCategory 删除附件分类(软删除)
|
||||
func DeleteAttachmentCategory(id uint) error {
|
||||
err := config.DB.Model(&models.AttachmentCategory{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting attachment category: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateOSSConfig 创建OSS配置
|
||||
func CreateOSSConfig(ossConfig *models.OSSConfig) error {
|
||||
err := config.DB.Create(ossConfig).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating OSS config: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOSSConfigs 获取所有OSS配置
|
||||
func GetOSSConfigs() ([]models.OSSConfig, error) {
|
||||
var configs []models.OSSConfig
|
||||
err := config.DB.Model(&models.OSSConfig{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("created_at DESC").
|
||||
Find(&configs).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error querying OSS configs: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
// GetOSSConfigByID 根据ID获取OSS配置
|
||||
func GetOSSConfigByID(id uint) (*models.OSSConfig, error) {
|
||||
var ossConfig models.OSSConfig
|
||||
err := config.DB.Model(&models.OSSConfig{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
First(&ossConfig).Error
|
||||
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error getting OSS config by ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ossConfig, nil
|
||||
}
|
||||
|
||||
// GetActiveOSSConfig 获取启用的OSS配置
|
||||
func GetActiveOSSConfig(storageType string) (*models.OSSConfig, error) {
|
||||
var ossConfig models.OSSConfig
|
||||
query := config.DB.Model(&models.OSSConfig{}).
|
||||
Where("deleted_at = ? AND is_active = ?", 0, 1)
|
||||
|
||||
if storageType != "" {
|
||||
query = query.Where("storage_type = ?", storageType)
|
||||
}
|
||||
|
||||
err := query.First(&ossConfig).Error
|
||||
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error getting active OSS config: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ossConfig, nil
|
||||
}
|
||||
|
||||
// UpdateOSSConfig 更新OSS配置
|
||||
func UpdateOSSConfig(ossConfig *models.OSSConfig) error {
|
||||
err := config.DB.Model(&models.OSSConfig{}).
|
||||
Where("id = ? AND deleted_at = ?", ossConfig.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"name": ossConfig.Name,
|
||||
"storage_type": ossConfig.StorageType,
|
||||
"access_key": ossConfig.AccessKey,
|
||||
"secret_key": ossConfig.SecretKey,
|
||||
"bucket": ossConfig.Bucket,
|
||||
"region": ossConfig.Region,
|
||||
"domain": ossConfig.Domain,
|
||||
"is_active": ossConfig.IsActive,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error updating OSS config: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteOSSConfig 删除OSS配置(软删除)
|
||||
func DeleteOSSConfig(id uint) error {
|
||||
err := config.DB.Model(&models.OSSConfig{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting OSS config: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -39,6 +39,72 @@ func GetColumnByID(id uint) (*models.Column, error) {
|
||||
return &col, nil
|
||||
}
|
||||
|
||||
// GetColumnStats 获取专栏统计信息(文章数量和最近更新时间)
|
||||
func GetColumnStats(columnID uint) (int64, int64, error) {
|
||||
var postCount int64
|
||||
|
||||
// 统计文章数量
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Joins("JOIN column_posts cp ON posts.id = cp.post_id").
|
||||
Where("cp.column_id = ? AND posts.deleted_at = ? AND posts.is_published = ?", columnID, 0, 1).
|
||||
Count(&postCount).Error
|
||||
if err != nil {
|
||||
log.Printf("Error counting posts for column: %v", err)
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
// 获取最近更新时间
|
||||
var lastUpdated int64
|
||||
if postCount > 0 {
|
||||
err = config.DB.Model(&models.Post{}).
|
||||
Select("COALESCE(MAX(posts.updated_at), 0)").
|
||||
Joins("JOIN column_posts cp ON posts.id = cp.post_id").
|
||||
Where("cp.column_id = ? AND posts.deleted_at = ? AND posts.is_published = ?", columnID, 0, 1).
|
||||
Scan(&lastUpdated).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting last updated time for column: %v", err)
|
||||
// 如果查询失败,使用专栏的更新时间
|
||||
var col models.Column
|
||||
if err2 := config.DB.Model(&models.Column{}).
|
||||
Select("updated_at").
|
||||
Where("id = ?", columnID).
|
||||
First(&col).Error; err2 == nil {
|
||||
lastUpdated = col.UpdatedAt
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果没有文章,使用专栏的更新时间
|
||||
var col models.Column
|
||||
if err := config.DB.Model(&models.Column{}).
|
||||
Select("updated_at").
|
||||
Where("id = ?", columnID).
|
||||
First(&col).Error; err == nil {
|
||||
lastUpdated = col.UpdatedAt
|
||||
}
|
||||
}
|
||||
|
||||
return postCount, lastUpdated, nil
|
||||
}
|
||||
|
||||
// BuildColumnResponse 构建专栏响应(包含统计信息)
|
||||
func BuildColumnResponse(col *models.Column) map[string]interface{} {
|
||||
postCount, lastUpdated, _ := GetColumnStats(col.ID)
|
||||
|
||||
return map[string]interface{}{
|
||||
"id": col.ID,
|
||||
"name": col.Name,
|
||||
"description": col.Description,
|
||||
"cover": col.Cover,
|
||||
"isActive": col.IsActive,
|
||||
"sortOrder": col.SortOrder,
|
||||
"createdAt": col.CreatedAt,
|
||||
"updatedAt": col.UpdatedAt,
|
||||
"deletedAt": col.DeletedAt,
|
||||
"postCount": postCount,
|
||||
"lastUpdated": lastUpdated,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateColumn 创建专栏
|
||||
func CreateColumn(col *models.Column) error {
|
||||
err := config.DB.Create(col).Error
|
||||
|
||||
@@ -18,10 +18,11 @@ type TrendData struct {
|
||||
}
|
||||
|
||||
// GetPosts 获取所有博客文章(支持搜索、分类、标签筛选)
|
||||
func GetPosts(keyword string, categoryID uint, tagID uint) ([]models.Post, error) {
|
||||
func GetPosts(keyword string, categoryID uint, tagID uint, columnID uint) ([]models.Post, error) {
|
||||
var posts []models.Post
|
||||
query := config.DB.Model(&models.Post{}).
|
||||
Preload("Category").
|
||||
Preload("Column").
|
||||
Preload("Tags").
|
||||
Where("is_published = ? AND deleted_at = ?", 1, 0)
|
||||
|
||||
@@ -34,6 +35,10 @@ func GetPosts(keyword string, categoryID uint, tagID uint) ([]models.Post, error
|
||||
query = query.Where("category_id = ?", categoryID)
|
||||
}
|
||||
|
||||
if columnID > 0 {
|
||||
query = query.Where("column_id = ?", columnID)
|
||||
}
|
||||
|
||||
if keyword != "" {
|
||||
likeKeyword := "%" + keyword + "%"
|
||||
query = query.Where("(MATCH(title, content) AGAINST(? IN BOOLEAN MODE) OR title LIKE ? OR content LIKE ?)",
|
||||
@@ -52,8 +57,11 @@ func GetPosts(keyword string, categoryID uint, tagID uint) ([]models.Post, error
|
||||
// GetPostByID 根据ID获取博客文章
|
||||
func GetPostByID(id uint) (*models.Post, error) {
|
||||
var post models.Post
|
||||
// 显式选择所有字段,确保 content 字段被加载
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Select("id", "title", "category_id", "column_id", "excerpt", "content", "read_count", "is_published", "created_at", "updated_at", "deleted_at").
|
||||
Preload("Category").
|
||||
Preload("Column").
|
||||
Preload("Tags").
|
||||
Where("id = ? AND is_published = ? AND deleted_at = ?", id, 1, 0).
|
||||
First(&post).Error
|
||||
@@ -93,6 +101,8 @@ func GetAllPosts(page, pageSize int) ([]models.Post, int64, error) {
|
||||
// Get posts
|
||||
err = config.DB.Model(&models.Post{}).
|
||||
Preload("Category").
|
||||
Preload("Column").
|
||||
Preload("Tags").
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
@@ -124,21 +134,41 @@ func CreatePost(post *models.Post) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle column association if ColumnID is set
|
||||
if post.ColumnID != nil && *post.ColumnID > 0 {
|
||||
// Add to column_posts table
|
||||
err = AddPostToColumn(*post.ColumnID, post.ID, 0)
|
||||
if err != nil {
|
||||
log.Printf("Error adding post to column: %v", err)
|
||||
// Don't fail the whole operation, just log
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePost 更新博客文章
|
||||
func UpdatePost(post *models.Post) error {
|
||||
updateData := map[string]interface{}{
|
||||
"title": post.Title,
|
||||
"category_id": post.CategoryID,
|
||||
"excerpt": post.Excerpt,
|
||||
"content": post.Content,
|
||||
"is_published": post.IsPublished,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}
|
||||
|
||||
// Update column_id if provided
|
||||
if post.ColumnID != nil {
|
||||
updateData["column_id"] = post.ColumnID
|
||||
} else {
|
||||
// Set to NULL if explicitly set to nil
|
||||
updateData["column_id"] = nil
|
||||
}
|
||||
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Where("id = ? AND deleted_at = ?", post.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"title": post.Title,
|
||||
"category_id": post.CategoryID,
|
||||
"excerpt": post.Excerpt,
|
||||
"content": post.Content,
|
||||
"is_published": post.IsPublished,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
Updates(updateData).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error updating post: %v", err)
|
||||
@@ -161,6 +191,24 @@ func UpdatePost(post *models.Post) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle column association
|
||||
// Always sync column_posts table with column_id
|
||||
// First, remove from all columns
|
||||
err = config.DB.Where("post_id = ?", post.ID).Delete(&models.ColumnPost{}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error removing post from columns: %v", err)
|
||||
// Don't fail the whole operation
|
||||
}
|
||||
|
||||
// Then add to new column if specified
|
||||
if post.ColumnID != nil && *post.ColumnID > 0 {
|
||||
err = AddPostToColumn(*post.ColumnID, post.ID, 0)
|
||||
if err != nil {
|
||||
log.Printf("Error adding post to column: %v", err)
|
||||
// Don't fail the whole operation
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -212,19 +260,34 @@ func BuildPostResponse(post *models.Post, includeContent bool) *models.PostRespo
|
||||
catSlug = post.Category.Slug
|
||||
}
|
||||
|
||||
colName := ""
|
||||
colSlug := ""
|
||||
if post.Column != nil {
|
||||
colName = post.Column.Name
|
||||
// 如果 Column 有 Slug 字段,可以在这里添加
|
||||
// colSlug = post.Column.Slug
|
||||
}
|
||||
|
||||
response := &models.PostResponse{
|
||||
ID: post.ID,
|
||||
Title: post.Title,
|
||||
CategoryID: post.CategoryID,
|
||||
CategoryName: catName,
|
||||
CategorySlug: catSlug,
|
||||
ColumnID: post.ColumnID,
|
||||
ColumnName: colName,
|
||||
ColumnSlug: colSlug,
|
||||
Date: dateStr,
|
||||
Excerpt: post.Excerpt,
|
||||
Tags: post.Tags,
|
||||
IsPublished: post.IsPublished,
|
||||
ReadCount: post.ReadCount,
|
||||
}
|
||||
|
||||
if includeContent {
|
||||
response.Content = post.Content
|
||||
// 使用指针类型,确保即使内容为空字符串也会出现在 JSON 中
|
||||
content := post.Content
|
||||
response.Content = &content
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
63
server/repositories/search_log_repository.go
Normal file
63
server/repositories/search_log_repository.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/niangaodev/art-code/config"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
)
|
||||
|
||||
// CreateSearchLog 创建搜索记录
|
||||
func CreateSearchLog(logEntry *models.SearchLog) error {
|
||||
err := config.DB.Create(logEntry).Error
|
||||
if err != nil {
|
||||
log.Printf("Error creating search log: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSearchLogs 获取搜索记录列表(分页)
|
||||
func GetSearchLogs(page, pageSize int) ([]models.SearchLog, int64, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var logs []models.SearchLog
|
||||
var total int64
|
||||
|
||||
// Count total
|
||||
err := config.DB.Model(&models.SearchLog{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&total).Error
|
||||
if err != nil {
|
||||
log.Printf("Error counting search logs: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Get logs
|
||||
err = config.DB.Model(&models.SearchLog{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&logs).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error querying search logs: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return logs, total, nil
|
||||
}
|
||||
|
||||
// DeleteSearchLog 删除搜索记录(软删除)
|
||||
func DeleteSearchLog(id uint) error {
|
||||
err := config.DB.Model(&models.SearchLog{}).
|
||||
Where("id = ?", id).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting search log: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
92
server/utils/encrypt.go
Normal file
92
server/utils/encrypt.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// AESKey 从环境变量或配置中获取,这里使用默认密钥(生产环境应该从配置读取)
|
||||
var defaultAESKey = []byte("your-32-byte-secret-key-here!!") // 32 bytes for AES-256
|
||||
|
||||
// GetAESKey 获取AES密钥(应该从配置文件或环境变量读取)
|
||||
func GetAESKey() []byte {
|
||||
// TODO: 从配置文件或环境变量读取密钥
|
||||
// key := os.Getenv("AES_ENCRYPTION_KEY")
|
||||
// if key == "" {
|
||||
// return defaultAESKey
|
||||
// }
|
||||
// return []byte(key)
|
||||
return defaultAESKey
|
||||
}
|
||||
|
||||
// EncryptAES 使用AES-256-GCM加密数据
|
||||
func EncryptAES(plaintext string) (string, error) {
|
||||
key := GetAESKey()
|
||||
|
||||
// Create cipher block
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create GCM
|
||||
aesGCM, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create nonce
|
||||
nonce := make([]byte, aesGCM.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Encrypt
|
||||
ciphertext := aesGCM.Seal(nonce, nonce, []byte(plaintext), nil)
|
||||
|
||||
// Encode to base64
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
// DecryptAES 使用AES-256-GCM解密数据
|
||||
func DecryptAES(encrypted string) (string, error) {
|
||||
key := GetAESKey()
|
||||
|
||||
// Decode from base64
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create cipher block
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create GCM
|
||||
aesGCM, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Extract nonce
|
||||
nonceSize := aesGCM.NonceSize()
|
||||
if len(ciphertext) < nonceSize {
|
||||
return "", errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||
|
||||
// Decrypt
|
||||
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(plaintext), nil
|
||||
}
|
||||
138
server/utils/oss.go
Normal file
138
server/utils/oss.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StorageType 存储类型
|
||||
type StorageType string
|
||||
|
||||
const (
|
||||
StorageLocal StorageType = "local"
|
||||
StorageQCloud StorageType = "qcloud"
|
||||
StorageAliyun StorageType = "aliyun"
|
||||
StorageQiniu StorageType = "qiniu"
|
||||
)
|
||||
|
||||
// OSSConfig OSS配置
|
||||
type OSSConfig struct {
|
||||
StorageType string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
Bucket string
|
||||
Region string
|
||||
Domain string
|
||||
}
|
||||
|
||||
// OSSUploader OSS上传接口
|
||||
type OSSUploader interface {
|
||||
Upload(file multipart.File, filename string, size int64) (string, string, error) // 返回 filePath, fileURL, error
|
||||
Delete(filePath string) error
|
||||
}
|
||||
|
||||
// GetOSSUploader 根据存储类型获取上传器
|
||||
func GetOSSUploader(config *OSSConfig) (OSSUploader, error) {
|
||||
switch StorageType(config.StorageType) {
|
||||
case StorageLocal:
|
||||
return &LocalUploader{
|
||||
BasePath: "./uploads",
|
||||
BaseURL: "/uploads",
|
||||
}, nil
|
||||
case StorageQCloud:
|
||||
// TODO: 实现腾讯云COS上传
|
||||
return nil, fmt.Errorf("qcloud storage not implemented yet")
|
||||
case StorageAliyun:
|
||||
// TODO: 实现阿里云OSS上传
|
||||
return nil, fmt.Errorf("aliyun storage not implemented yet")
|
||||
case StorageQiniu:
|
||||
// TODO: 实现七牛云上传
|
||||
return nil, fmt.Errorf("qiniu storage not implemented yet")
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported storage type: %s", config.StorageType)
|
||||
}
|
||||
}
|
||||
|
||||
// LocalUploader 本地存储上传器
|
||||
type LocalUploader struct {
|
||||
BasePath string
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
// Upload 上传文件到本地
|
||||
func (l *LocalUploader) Upload(file multipart.File, filename string, size int64) (string, string, error) {
|
||||
// 生成唯一文件名
|
||||
ext := filepath.Ext(filename)
|
||||
timestamp := time.Now().Unix()
|
||||
randomStr := fmt.Sprintf("%d", timestamp)
|
||||
newFilename := fmt.Sprintf("%s_%s%s", strings.TrimSuffix(filename, ext), randomStr, ext)
|
||||
|
||||
// 按日期创建目录
|
||||
dateDir := time.Now().Format("2006/01/02")
|
||||
uploadDir := filepath.Join(l.BasePath, dateDir)
|
||||
|
||||
// 创建目录
|
||||
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
||||
return "", "", fmt.Errorf("failed to create upload directory: %v", err)
|
||||
}
|
||||
|
||||
// 完整文件路径
|
||||
filePath := filepath.Join(uploadDir, newFilename)
|
||||
|
||||
// 创建目标文件
|
||||
dst, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to create file: %v", err)
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
// 复制文件内容
|
||||
if _, err := io.Copy(dst, file); err != nil {
|
||||
return "", "", fmt.Errorf("failed to copy file: %v", err)
|
||||
}
|
||||
|
||||
// 生成访问URL
|
||||
fileURL := fmt.Sprintf("%s/%s/%s", l.BaseURL, dateDir, newFilename)
|
||||
|
||||
return filePath, fileURL, nil
|
||||
}
|
||||
|
||||
// Delete 删除本地文件
|
||||
func (l *LocalUploader) Delete(filePath string) error {
|
||||
// 确保文件路径在BasePath内(安全措施)
|
||||
absBasePath, err := filepath.Abs(l.BasePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
absFilePath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(absFilePath, absBasePath) {
|
||||
return fmt.Errorf("invalid file path: outside base directory")
|
||||
}
|
||||
|
||||
return os.Remove(filePath)
|
||||
}
|
||||
|
||||
// GetFileType 根据MIME类型判断文件类型
|
||||
func GetFileType(mimeType string) string {
|
||||
if strings.HasPrefix(mimeType, "image/") {
|
||||
return "image"
|
||||
} else if strings.HasPrefix(mimeType, "video/") {
|
||||
return "video"
|
||||
} else if strings.HasPrefix(mimeType, "application/pdf") ||
|
||||
strings.HasPrefix(mimeType, "application/msword") ||
|
||||
strings.HasPrefix(mimeType, "application/vnd.openxmlformats") ||
|
||||
strings.HasPrefix(mimeType, "text/") {
|
||||
return "document"
|
||||
}
|
||||
return "other"
|
||||
}
|
||||
Reference in New Issue
Block a user