Merge remote-tracking branch 'upstream/feature/1.7.1' into feature/1.7.1

# Conflicts:
#	neutrino-proxy-admin/src/views/proxy/portMapping.vue
#	neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/PortPoolService.java
This commit is contained in:
Yohanes
2023-03-20 10:11:47 +08:00
139 changed files with 6124 additions and 434 deletions
+31
View File
@@ -148,6 +148,37 @@ neutrino.proxy.client.license-key=b0a907332b474b25897c4dcb31fc7eb6
- 微信: yuyunshize
- Gitee: https://gitee.com/asgc/neutrino-proxy
# 10、贡献者列表
<p>
<a href="https://gitee.com/zcans" target="_blank">
<img src="https://neutrino-proxy.oss-cn-hangzhou.aliyuncs.com/developer/zcans.png" width="12%">
</a>
<a href="https://gitee.com/bmlt" target="_blank">
<img src="https://neutrino-proxy.oss-cn-hangzhou.aliyuncs.com/developer/jiuye.png" width="12%">
</a>
<a href="https://gitee.com/wangke666" target="_blank">
<img src="https://neutrino-proxy.oss-cn-hangzhou.aliyuncs.com/developer/keke.png" width="12%">
</a>
<a href="https://gitee.com/xue-fangming" target="_blank">
<img src="https://neutrino-proxy.oss-cn-hangzhou.aliyuncs.com/developer/aa.png" width="12%">
</a>
<a href="https://gitee.com/noear_admin" target="_blank">
<img src="https://neutrino-proxy.oss-cn-hangzhou.aliyuncs.com/developer/near.png" width="12%">
</a>
<a href="https://gitee.com/westboy" target="_blank">
<img src="https://neutrino-proxy.oss-cn-hangzhou.aliyuncs.com/developer/1679066920230.jpg" width="12%">
</a>
<a href="https://gitee.com/liugddx" target="_blank">
<img src="https://neutrino-proxy.oss-cn-hangzhou.aliyuncs.com/developer/liugddx.png" width="12%">
</a>
<a href="https://gitee.com/DianZiFaPiao" target="_blank">
<img src="https://neutrino-proxy.oss-cn-hangzhou.aliyuncs.com/developer/tan90.png" width="12%">
</a>
<a href="https://gitee.com/dearsny" target="_blank">
<img src="https://neutrino-proxy.oss-cn-hangzhou.aliyuncs.com/developer/pingon.png" width="12%">
</a>
</p>
# ❤️ 感谢
* [Solon](https://gitee.com/noear/solon?from=NeutrinoProxy)
* [Hutool](https://hutool.cn?from=NeutrinoProxy)
+44
View File
@@ -0,0 +1,44 @@
#!/bin/sh
# 中微子代理管理后台编译打包脚本,基础参数请自行修改
#环境
env=dev
nvmDir=$HOME/.nvm
nodeVersion=v13.12.0
deployDir="deploy"
serverDeployDir=$deployDir"/server"
adminDeployDir=$serverDeployDir"/neutrino-proxy-admin"
giteePagesDir=$deployDir"/gitee-pages"
#设置nvm生效
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
#初始化文件夹
cd ..
rm -rf $adminDeployDir
if [ ! -d "$deployDir" ];then
mkdir $deployDir
fi
if [ ! -d "$serverDeployDir" ];then
mkdir $serverDeployDir
fi
if [ ! -d "$adminDeployDir" ];then
mkdir $adminDeployDir
fi
#切node版本
nvm use $nodeVersion
#进入admin项目目录
cd ./neutrino-proxy-admin
#删除之前的build
rm -rf ./dist
#安装依赖
npm i
#编译
npm run build:$env
#拷贝
cd ..
cp -rf ./neutrino-proxy-admin/dist $adminDeployDir/
cp -rf ./neutrino-proxy-admin/dist/ $giteePagesDir
@@ -8,6 +8,7 @@ nodeVersion=v13.12.0
deployDir="deploy"
serverDeployDir=$deployDir"/server"
adminDeployDir=$serverDeployDir"/neutrino-proxy-admin"
giteePagesDir=$deployDir"/gitee-pages"
#设置nvm生效
export NVM_DIR="$HOME/.nvm"
@@ -40,3 +41,4 @@ npm run build:$env
#拷贝
cd ..
cp -rf ./neutrino-proxy-admin/dist $adminDeployDir/
cp -rf ./neutrino-proxy-admin/dist/ $giteePagesDir
+8
View File
@@ -0,0 +1,8 @@
# 管理后台问题汇总
## 问题1...
> 描述、解决方式
# 代理服务端问题汇总
# 代理客户端问题汇总
+45
View File
@@ -0,0 +1,45 @@
import request from '@/utils/request'
export function fetchList(query) {
return request({
url: '/port-group/page',
method: 'get',
params: query
})
}
export function portGroupList() {
return request({
url: '/port-group/list',
method: 'get'
})
}
export function updateEnableStatus(id, enable) {
return request({
url: '/port-group/update/enable-status',
method: 'post',
data: {
id: id,
enable: enable
}
})
}
export function createPortGroup(data) {
return request({
url: '/port-group/create',
method: 'post',
data
})
}
export function deleteGroup(id) {
return request({
url: '/port-group/delete',
method: 'post',
params: {
id: id
}
})
}
+9
View File
@@ -14,6 +14,15 @@ export function portPoolList() {
method: 'get'
})
}
export function availablePortList(licenseId) {
return request({
url: '/port-pool/get-available-port-list',
method: 'get',
params: {
licenseId: licenseId
}
})
}
export function updateEnableStatus(id, enable) {
return request({
+16
View File
@@ -0,0 +1,16 @@
import request from '@/utils/request'
export function fetchUserFlowReportList(query) {
return request({
url: '/report/user/flow-report/page',
method: 'get',
params: query
})
}
export function fetchLicenseFlowReportList(query) {
return request({
url: '/report/license/flow-report/page',
method: 'get',
params: query
})
}
+14 -2
View File
@@ -48,6 +48,7 @@ export default {
user: '用户管理',
system: '系统管理',
portPool: '端口池管理',
portGroup: '端口分组管理',
proxy: '代理配置',
license: 'License管理',
portMapping: '端口映射',
@@ -55,7 +56,10 @@ export default {
jobLog: '调度日志',
log: '日志管理',
loginLog: '登录日志',
clientConnectLog: '客户端连接日志'
clientConnectLog: '客户端连接日志',
report: '报表管理',
userFlowReport: '用户流量报表',
licenseFlowReport: 'License流量报表'
},
navbar: {
logOut: '退出登录',
@@ -118,6 +122,11 @@ export default {
confirm: '确 定',
userId: '用户ID',
userName: '用户名',
group: '分组',
groupName: '分组名称',
groupPossessor: '分组所属',
possessorType: '所属类',
possessorId: '分组所属',
license: 'License',
createTime: '创建时间',
updateTime: '更新时间',
@@ -147,7 +156,10 @@ export default {
msg: '消息',
outcome: '结果',
err: '异常信息',
resetKey: '重置Key'
resetKey: '重置Key',
upFlow: '上行流量',
downFlow: '下行流量',
totalFlow: '总流量'
},
button: {
lookOver: '查看'
+15
View File
@@ -75,10 +75,25 @@ export const asyncRouterMap = [
},
children: [
{ path: 'user', component: _import('system/user'), name: 'user', meta: { title: 'user' }},
{ path: 'portGroup', component: _import('system/portGroup'), name: 'portGroup', meta: { title: 'portGroup' }},
{ path: 'portPool', component: _import('system/portPool'), name: 'portPool', meta: { title: 'portPool' }},
{ path: 'jobManager', component: _import('system/jobManager'), name: 'jobManager', meta: { title: 'jobManager' }}
]
},
{
path: '/report',
component: Layout,
redirect: 'noredirect',
name: 'report',
meta: {
title: 'report',
icon: 'component'
},
children: [
{ path: 'userFlowReport', component: _import('report/userFlowReport'), name: 'userFlowReport', meta: { title: 'userFlowReport' }},
{ path: 'licenseFlowReport', component: _import('report/licenseFlowReport'), name: 'licenseFlowReport', meta: { title: 'licenseFlowReport' }}
]
},
{
path: '/log',
component: Layout,
@@ -1,6 +1,15 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-select v-model="listQuery.isOnline" placeholder="请选择在线状态" clearable>
<el-option v-for="item in selectObj.onlineOptions" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
<el-select v-model="listQuery.enable" placeholder="请选择启用状态" clearable>
<el-option v-for="item in selectObj.statusOptions" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary" icon="el-icon-edit">{{$t('table.add')}}</el-button>
</div>
@@ -134,9 +143,9 @@
listQuery: {
current: 1,
size: 10,
importance: undefined,
title: undefined,
type: undefined
enable: undefined,
isOnline: undefined,
userId: null
},
importanceOptions: [1, 2, 3],
calendarTypeOptions,
@@ -165,7 +174,11 @@
userId: [{ required: true, message: '请选择用户', trigger: 'blur' }],
name: [{ required: true, message: 'License名称不能为空', trigger: 'blur' }]
},
downloadLoading: false
downloadLoading: false,
selectObj: {
statusOptions: [{ label: '启用', value: 1 }, { label: '禁用', value: 2 }],
onlineOptions: [{ label: '在线', value: 1 }, { label: '离线', value: 2 }]
}
}
},
filters: {
@@ -195,8 +208,7 @@
}
},
created() {
this.getList()
this.getUserList()
this.getDataList()
},
methods: {
getList() {
@@ -212,6 +224,17 @@
this.userList = response.data.data
})
},
getDataList() {
const loginName = this.$store.state.user.loginName
userList().then(response => {
this.userList = response.data.data
const curUser = this.userList.find((val) => val.loginName === loginName)
if (curUser) {
this.listQuery.userId = curUser.id
}
this.getList()
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
@@ -20,7 +20,7 @@
<el-table-column align="center" :label="$t('table.id')" width="50">
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.userName')" width="100">
<template slot-scope="scope">
@@ -93,10 +93,17 @@
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item :label="$t('License')" prop="licenseId">
<DropdownTable v-model="temp.licenseId" :name.sync="temp.licenseName" :tableData="licenseList"
@selectedData="selectedFeeItem" placeholder="请选择" :width="280" :disabled="dialogStatus === 'update'" />
<!-- <DropdownTable
<el-form-item :label="$t('License')" prop="licenseId" >
<DropdownTable
v-model="temp.licenseId"
:name.sync="temp.licenseName"
:tableData= "licenseList"
@selectedData="selectedFeeItem"
placeholder="请选择"
:width="280"
:disabled="dialogStatus==='update'"
/>
<!-- <DropdownTable
:columns="countryColumns"
:data="licenseList"
v-model="temp.licenseId"
@@ -141,14 +148,14 @@
</template>
<script>
import { fetchList, createUserPortMapping, updateUserPortMapping, updateEnableStatus, deletePortMapping } from '@/api/portMapping'
import { portPoolList } from '@/api/portPool'
import { licenseList } from '@/api/license'
import { userList } from '@/api/user'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
import DropdownTable from '../../components/Dropdown/DropdownTable'
import { fetchList, createUserPortMapping, updateUserPortMapping, updateEnableStatus, deletePortMapping } from '@/api/portMapping'
import { portPoolList, availablePortList } from '@/api/portPool'
import { licenseList } from '@/api/license'
import { userList } from '@/api/user'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
import DropdownTable from '../../components/Dropdown/DropdownTable'
const calendarTypeOptions = [
{ key: 'CN', display_name: 'China' },
@@ -163,223 +170,246 @@ const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
return acc
}, {})
export default {
name: 'complexTable',
directives: {
waves
},
components: {
DropdownTable,
ButtonPopover
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: true,
listQuery: {
current: 1,
size: 10,
importance: undefined,
title: undefined,
type: undefined
},
importanceOptions: [1, 2, 3],
calendarTypeOptions,
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
statusOptions: ['published', 'draft', 'deleted'],
userList: [],
licenseList: [],
serverPortList: [],
showReviewer: false,
temp: {
id: undefined,
licenseId: undefined,
licenseName: undefined,
serverPort: undefined,
clientIp: undefined,
clientPort: undefined
},
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: '编辑',
create: '新建'
},
dialogPvVisible: false,
pvData: [],
rules: {
licenseId: [{ required: true, message: '请选择License', trigger: 'blur,change' }],
serverPort: [{ required: true, message: '请输入服务端端口', trigger: 'blur' }],
clientIp: [{ required: true, message: '请输入客户端IP', trigger: 'blur' }],
clientPort: [{ required: true, message: '请输入客户端端口', trigger: 'blur' }]
},
downloadLoading: false,
countryColumns: [
{ prop: 'userName', label: '用户名', align: 'center' },
{ prop: 'name', label: 'License', align: 'center' }
]
}
},
filters: {
statusName(status) {
const statusMap = {
1: '启用',
2: '禁用'
export default {
name: 'complexTable',
directives: {
waves
},
components: {
DropdownTable,
ButtonPopover
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: true,
listQuery: {
current: 1,
size: 10,
importance: undefined,
title: undefined,
type: undefined
},
importanceOptions: [1, 2, 3],
calendarTypeOptions,
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
statusOptions: ['published', 'draft', 'deleted'],
userList: [],
licenseList: [],
serverPortList: [],
showReviewer: false,
temp: {
id: undefined,
licenseId: undefined,
licenseName: undefined,
serverPort: undefined,
clientIp: undefined,
clientPort: undefined
},
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: '编辑',
create: '新建'
},
dialogPvVisible: false,
pvData: [],
rules: {
licenseId: [{ required: true, message: '请选择License', trigger: 'blur,change' }],
serverPort: [{ required: true, message: '请输入服务端端口', trigger: 'blur' }],
clientIp: [{ required: true, message: '请输入客户端IP', trigger: 'blur' }],
clientPort: [{ required: true, message: '请输入客户端端口', trigger: 'blur' }]
},
downloadLoading: false,
countryColumns: [
{ prop: 'userName', label: '用户名', align: 'center' },
{ prop: 'name', label: 'License', align: 'center' }
]
}
return statusMap[status]
},
isOnlineName(isOnline) {
const isOnlineMap = {
1: '在线',
2: '离线'
}
return isOnlineMap[isOnline]
},
statusFilter(status) {
const statusMap = {
1: 'success',
2: 'danger'
}
return statusMap[status]
},
typeFilter(type) {
return calendarTypeKeyValue[type]
}
},
created() {
this.getList()
this.getPortPoolList()
this.getLicenseList()
this.getAllUserList()
},
methods: {
getList() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
this.list = response.data.data.records
this.total = response.data.data.total
this.listLoading = false
})
},
getPortPoolList() {
portPoolList().then(response => {
this.serverPortList = response.data.data
})
},
getAllUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
getLicenseList() {
licenseList().then(response => {
this.licenseList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleModifyStatus(row, enable) {
console.log('route', this.$route)
updateEnableStatus(row.id, enable).then(response => {
if (response.data.data.code === 0) {
this.$message({
message: '操作成功',
type: 'success'
})
filters: {
statusName(status) {
const statusMap = {
1: '启用',
2: '禁用'
}
return statusMap[status]
},
isOnlineName(isOnline) {
const isOnlineMap = {
1: '在线',
2: '离线'
}
return isOnlineMap[isOnline]
},
statusFilter(status) {
const statusMap = {
1: 'success',
2: 'danger'
}
return statusMap[status]
},
typeFilter(type) {
return calendarTypeKeyValue[type]
}
},
created() {
this.getList()
this.getPortPoolList()
this.getLicenseList()
this.getAllUserList()
},
methods: {
getList() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
this.list = response.data.data.records
this.total = response.data.data.total
this.listLoading = false
})
},
getPortPoolList() {
portPoolList().then(response => {
this.serverPortList = response.data.data
})
},
getAvailablePortList(licenseId) {
availablePortList(licenseId).then(response => {
this.serverPortList = response.data.data
})
},
getAllUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
getLicenseList() {
licenseList().then(response => {
this.licenseList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
})
},
resetTemp() {
this.temp = {
id: undefined,
licenseId: undefined,
licenseName: undefined,
serverPort: undefined,
clientIp: '127.0.0.1',
clientPort: undefined
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
createUserPortMapping(this.temp).then(response => {
},
handleSizeChange(val) {
this.listQuery.size = val
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleModifyStatus(row, enable) {
console.log('route', this.$route)
updateEnableStatus(row.id, enable).then(response => {
if (response.data.data.code === 0) {
this.$message({
message: '操作成功',
type: 'success'
})
}
this.getList()
})
},
resetTemp() {
this.temp = {
id: undefined,
licenseId: undefined,
licenseName: undefined,
serverPort: undefined,
clientIp: '127.0.0.1',
clientPort: undefined
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
createUserPortMapping(this.temp).then(response => {
if (response.data.code === 0) {
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.temp.timestamp = new Date(this.temp.timestamp)
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
updateUserPortMapping(tempData).then(response => {
if (response.data.code === 0) {
// this.$message({
// message: '操作成功',
// type: 'success'
// })
this.$notify({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000
})
this.dialogFormVisible = false
this.getList()
}
})
}
})
},
selectedFeeItem(row, list) {
if (this.temp.licenseId !== row.id) {
this.temp.licenseId = row.id
this.temp.licenseName = row.name
this.getAvailablePortList(row.id)
this.temp.serverPort = null
}
},
handleDelete(row) {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deletePortMapping(row.id).then(response => {
if (response.data.code === 0) {
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '创建成功',
message: '删除成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.temp.timestamp = new Date(this.temp.timestamp)
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
updateUserPortMapping(tempData).then(response => {
if (response.data.code === 0) {
// this.$message({
// message: '操作成功',
// type: 'success'
// })
this.$notify({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000
})
this.dialogFormVisible = false
this.getList()
}
})
}
})
},
selectedFeeItem(row, list) {
this.temp.licenseId = row.id
this.temp.licenseName = row.name
},
handleDelete(row) {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
}).catch(() => {})
},
handleDelete2(row) {
deletePortMapping(row.id).then(response => {
if (response.data.code === 0) {
this.$notify({
@@ -391,40 +421,26 @@ export default {
this.getList()
}
})
}).catch(() => { })
},
handleDelete2(row) {
deletePortMapping(row.id).then(response => {
if (response.data.code === 0) {
this.$notify({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
const data = this.formatJson(filterVal, this.list)
excel.export_json_to_excel(tHeader, data, 'table-list')
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
const data = this.formatJson(filterVal, this.list)
excel.export_json_to_excel(tHeader, data, 'table-list')
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
}
}
}
}
</script>
@@ -0,0 +1,128 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-button type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
</div>
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row style="width: 100%">
<el-table-column type="index" width="100" :label="$t('table.id')"></el-table-column>
<el-table-column align="center" :label="$t('table.userName')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.userName}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.licenseName')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.licenseName}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.upFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.upFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.downFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.downFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.totalFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.totalFlowDesc}}</span>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
</div>
</template>
<script>
import { fetchLicenseFlowReportList } from '@/api/report'
import { userList } from '@/api/user'
import waves from '@/directive/waves'
export default {
name: 'jobLog',
directives: {
waves
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: false,
listQuery: {
current: 1,
size: 10,
jobId: undefined
},
userList: [],
dialogVisible: false,
selectRow: {}
}
},
filters: {
},
created() {
this.getList()
this.getUserList()
},
activated() {
this.getUserList()
if (this.$route.query.jobId) {
this.listQuery.jobId = this.$route.query.jobId
this.getList()
}
},
methods: {
getList() {
this.listLoading = true
fetchLicenseFlowReportList(this.listQuery).then(response => {
this.list = response.data.data.records
this.total = response.data.data.total
this.listLoading = false
})
},
getUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.listQuery.current = 1
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleShowClick(row) {
console.log(row)
},
handleLookOver(row) {
this.selectRow = row
this.dialogVisible = true
}
}
}
</script>
<style>
.job-msg-div{
max-height: 400px;
overflow-y: auto;
}
</style>
@@ -0,0 +1,123 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-button type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
</div>
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row style="width: 100%">
<el-table-column type="index" width="100" :label="$t('table.id')"></el-table-column>
<el-table-column align="center" :label="$t('table.userName')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.userName}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.upFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.upFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.downFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.downFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.totalFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.totalFlowDesc}}</span>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
</div>
</template>
<script>
import { fetchUserFlowReportList } from '@/api/report'
import { userList } from '@/api/user'
import waves from '@/directive/waves' // 水波纹指令
export default {
name: 'jobLog',
directives: {
waves
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: false,
listQuery: {
current: 1,
size: 10,
jobId: undefined
},
userList: [],
dialogVisible: false,
selectRow: {}
}
},
filters: {
},
created() {
this.getList()
this.getUserList()
},
activated() {
this.getUserList()
if (this.$route.query.jobId) {
this.listQuery.jobId = this.$route.query.jobId
this.getList()
}
},
methods: {
getList() {
this.listLoading = true
fetchUserFlowReportList(this.listQuery).then(response => {
this.list = response.data.data.records
this.total = response.data.data.total
this.listLoading = false
})
},
getUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.listQuery.current = 1
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleShowClick(row) {
console.log(row)
},
handleLookOver(row) {
this.selectRow = row
this.dialogVisible = true
}
}
}
</script>
<style>
.job-msg-div{
max-height: 400px;
overflow-y: auto;
}
</style>
@@ -0,0 +1,346 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">
{{ $t('table.search') }}
</el-button>
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary"
icon="el-icon-edit">{{ $t('table.add') }}
</el-button>
</div>
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit
highlight-current-row
style="width: 100%">
<el-table-column align="center" :label="$t('table.id')" width="120">
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.groupName')" width="200">
<template slot-scope="scope">
<span>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.groupPossessor')" width="200">
<template slot-scope="scope">
<span>{{ scope.row.possessorType == 1 ? "用户:" : (scope.row.possessorType == 2 ? "License:" : "全局") }} {{ scope.row.possessor }}</span>
</template>
</el-table-column>
<el-table-column width="200" align="center" :label="$t('table.createTime')">
<template slot-scope="scope">
<span>{{ scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column width="200" align="center" :label="$t('table.updateTime')">
<template slot-scope="scope">
<span>{{ scope.row.updateTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<!-- <el-table-column class-name="status-col" :label="$t('table.enableStatus')" width="150">-->
<!-- <template slot-scope="scope">-->
<!-- <el-tag :type="scope.row.enable | statusFilter">{{ scope.row.enable | statusName }}</el-tag>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column align="center" :label="$t('table.actions')" width="250" class-name="small-padding fixed-width">
<template slot-scope="scope">
<!-- <el-button v-if="scope.row.enable =='1'" size="mini" type="danger" @click="handleModifyStatus(scope.row,2)">-->
<!-- {{ $t('table.disable') }}-->
<!-- </el-button>-->
<!-- <el-button v-if="scope.row.enable =='2'" size="mini" type="success" @click="handleModifyStatus(scope.row,1)">-->
<!-- {{ $t('table.enable') }}-->
<!-- </el-button>-->
<!-- <el-button v-if="scope.row.status!='deleted'" size="mini" type="danger" @click="handleDelete(scope.row,'deleted')">{{$t('table.delete')}}</el-button>-->
<ButtonPopover @handleCommitClick="handleDelete(scope.row)" style="margin-left: 10px"/>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-pageInfo.sync="listQuery.current"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size"
layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form :rules="rules" ref="dataForm" :model="temp" label-position="left" label-width="70px"
style='width: 400px; margin-left:50px;'>
<el-form-item style="width: 350px" class="filter-item" :label="$t('table.groupName')" prop="name">
<el-input v-model="temp.name"></el-input>
</el-form-item>
<el-form-item :label="$t('table.possessorType')" prop="possessorType">
<el-select style="width: 280px" class="filter-item" v-model="temp.possessorType" placeholder="请选择"
:disabled="dialogStatus=='update'">
<el-option v-for="item in possessorTypeList" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item v-show="temp.possessorType==1" :label="$t('table.userName')" prop="possessorId">
<el-select style="width: 280px" class="filter-item" v-model="temp.possessorId" placeholder="请选择"
:disabled="dialogStatus=='update'">
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item v-show="temp.possessorType==2" :label="$t('table.license')" prop="possessorId">
<el-select style="width: 280px" class="filter-item" v-model="temp.possessorId" placeholder="请选择"
:disabled="dialogStatus=='update'">
<el-option v-for="item in licenseList" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">{{ $t('table.cancel') }}</el-button>
<el-button v-if="dialogStatus=='create'" type="primary" @click="createData">{{ $t('table.confirm') }}
</el-button>
</div>
</el-dialog>
<el-dialog title="Reading statistics" :visible.sync="dialogPvVisible">
<el-table :data="pvData" border fit highlight-current-row style="width: 100%">
<el-table-column prop="key" label="Channel"></el-table-column>
<el-table-column prop="pv" label="Pv"></el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogPvVisible = false">{{ $t('table.confirm') }}</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { fetchList, updateEnableStatus, createPortGroup, deleteGroup } from '@/api/portGroup'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import { userList } from '@/api/user'
import ButtonPopover from '../../components/Button/buttonPopover'
import { licenseList } from '../../api/license'
const calendarTypeOptions = [
{ key: 'CN', display_name: 'China' },
{ key: 'US', display_name: 'USA' },
{ key: 'JP', display_name: 'Japan' },
{ key: 'EU', display_name: 'Eurozone' }
]
// arr to obj ,such as { CN : "China", US : "USA" }
const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
acc[cur.key] = cur.display_name
return acc
}, {})
export default {
name: 'complexTable',
directives: {
waves
},
components: {
ButtonPopover
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: true,
listQuery: {
current: 1,
size: 10,
importance: undefined,
title: undefined,
type: undefined
},
importanceOptions: [1, 2, 3],
calendarTypeOptions,
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
statusOptions: ['published', 'draft', 'deleted'],
showReviewer: false,
userList: [],
licenseList: [],
possessorTypeList: [
{
'id': 1,
'name': '用户'
},
{
'id': 2,
'name': '通道'
}
],
temp: {
name: '',
possessorType: 1,
possessorId: 1
},
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: '编辑',
create: '新建'
},
dialogPvVisible: false,
pvData: [],
rules: {
port: [{ required: true, message: '端口必填', trigger: 'blur' }]
},
downloadLoading: false
}
},
filters: {
statusName(status) {
const statusMap = {
1: '启用',
2: '禁用'
}
return statusMap[status]
},
statusFilter(status) {
const statusMap = {
1: 'success',
2: 'danger'
}
return statusMap[status]
},
typeFilter(type) {
return calendarTypeKeyValue[type]
}
},
created() {
this.getList()
this.getUserList()
this.getLicenseList()
},
methods: {
getList() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
this.list = response.data.data.records
this.total = response.data.data.total
this.listLoading = false
})
},
getUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
getLicenseList() {
licenseList().then(response => {
this.licenseList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleModifyStatus(row, enable) {
console.log('route', this.$route)
updateEnableStatus(row.id, enable).then(response => {
if (response.data.data.code === 0) {
this.$message({
message: '操作成功',
type: 'success'
})
}
this.getList()
})
},
resetTemp() {
this.temp = {
id: undefined,
importance: 1,
remark: '',
timestamp: new Date(),
title: '',
status: 'published',
type: ''
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
createPortGroup(this.temp).then(response => {
if (response.data.code === 0) {
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.temp.timestamp = new Date(this.temp.timestamp)
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
handleDelete(row) {
deleteGroup(row.id).then(response => {
if (response.data.code === 0) {
this.$notify({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
const data = this.formatJson(filterVal, this.list)
excel.export_json_to_excel(tHeader, data, 'table-list')
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
}
}
}
</script>
@@ -17,6 +17,11 @@
<span>{{scope.row.port}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.groupName')" width="200">
<template slot-scope="scope">
<span>{{scope.row.groupName}}</span>
</template>
</el-table-column>
<el-table-column width="200" align="center" :label="$t('table.createTime')">
<template slot-scope="scope">
<span>{{scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
@@ -53,6 +58,15 @@
<el-form-item :label="$t('table.port')" prop="port">
<el-input v-model="temp.port"></el-input>
</el-form-item>
<el-form-item :label="$t('table.group')" prop="group">
<el-select style="width: 330px" class="filter-item" v-model="temp.groupId"
:disabled="dialogStatus=='update'">
<el-option v-for="item in portGroupList" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">{{$t('table.cancel')}}</el-button>
@@ -75,6 +89,7 @@
<script>
import { fetchList, updateEnableStatus, createPortPool, deletePortPool } from '@/api/portPool'
import { portGroupList } from '@/api/portGroup'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
@@ -119,6 +134,7 @@
statusOptions: ['published', 'draft', 'deleted'],
showReviewer: false,
temp: {
groupId: 1,
id: undefined,
importance: 1,
remark: '',
@@ -133,6 +149,7 @@
update: '编辑',
create: '新建'
},
portGroupList: [],
dialogPvVisible: false,
pvData: [],
rules: {
@@ -161,7 +178,9 @@
}
},
created() {
this.getList()
// eslint-disable-next-line no-sequences
this.getList(),
this.getPortGroupList()
},
methods: {
getList() {
@@ -172,6 +191,11 @@
this.listLoading = false
})
},
getPortGroupList() {
portGroupList().then(response => {
this.portGroupList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
@@ -211,6 +235,7 @@
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.temp.groupId = 1
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
@@ -45,7 +45,7 @@ import java.util.List;
@Slf4j
@Component
public class DBInitialize implements EventListener<AppLoadEndEvent> {
private static List<String> initDataTableNameList = Lists.newArrayList("user", "license", "port_pool", "port_mapping", "job_info");
private static List<String> initDataTableNameList = Lists.newArrayList("user", "license", "port_group", "port_pool", "port_mapping", "job_info");
@Inject
private DbConfig dbConfig;
@@ -0,0 +1,12 @@
package fun.asgc.neutrino.proxy.server.constant;
/**
* @author: aoshiguchen
* @date: 2023/3/18
*/
public interface Constants {
/**
* 默认的端口分组ID
*/
int DEFAULT_PORT_GROUP_ID = 1;
}
@@ -57,6 +57,10 @@ public enum ExceptionConstant {
// 调度管理(15000)
JOB_INFO_NOT_EXIST(15000, "调度管理记录不存在"),
SYSTEM_ERROR(500, "系统异常"),
PORT_GROUP_NAME_ALREADY_EXIST(16000,"端口分组名称[{}]已经存在"),
PORT_GROUP_NAME_DOES_NOT_EXIST(16001,"端口分组不存在"),
DEFAULT_GROUP_FORBID_DELETE(16002,"默认分组禁止删除")
;
private int code;
@@ -0,0 +1,73 @@
package fun.asgc.neutrino.proxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
import fun.asgc.neutrino.proxy.server.controller.req.*;
import fun.asgc.neutrino.proxy.server.controller.res.*;
import fun.asgc.neutrino.proxy.server.service.PortGroupService;
import fun.asgc.neutrino.proxy.server.service.PortPoolService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.apache.commons.lang3.StringUtils;
import org.noear.solon.annotation.*;
import java.util.List;
/**
* 端口分组控制层
*/
@Mapping("/port-group")
@Controller
public class PortGroupController {
@Inject
private PortGroupService portGroupService;
@Post
@Mapping("/create")
public PortGroupCreateRes create(PortGroupCreateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotEmpty(req.getName(), "name");
ParamCheckUtil.checkNotNull(req.getPossessorType(), "possessorType");
ParamCheckUtil.checkNotNull(req.getPossessorId(), "possessorId");
return portGroupService.create(req);
}
@Get
@Mapping("/page")
public PageInfo<PortGroupListRes> page(PageQuery pageQuery, PortGroupListReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return portGroupService.page(pageQuery, req);
}
@Get
@Mapping("/list")
public List<PortGroupListRes> list(PortGroupListReq req) {
return portGroupService.list(req);
}
@Post
@Mapping("/update/enable-status")
public PortGroupUpdateEnableStatusRes updateEnableStatus(PortGroupUpdateEnableStatusReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getId(), "id");
ParamCheckUtil.checkNotNull(req.getEnable(), "enable");
return portGroupService.updateEnableStatus(req);
}
@Post
@Mapping("/delete")
@Authorization(onlyAdmin = true)
public void delete(PortGroupDeleteReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getId(), "id");
portGroupService.delete(req.getId());
}
}
@@ -24,13 +24,8 @@ package fun.asgc.neutrino.proxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolCreateReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolDeleteReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolListReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolCreateRes;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolListRes;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolUpdateEnableStatusRes;
import fun.asgc.neutrino.proxy.server.controller.req.*;
import fun.asgc.neutrino.proxy.server.controller.res.*;
import fun.asgc.neutrino.proxy.server.service.PortPoolService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.noear.solon.annotation.*;
@@ -68,6 +63,7 @@ public class PortPoolController {
public PortPoolCreateRes create(PortPoolCreateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getPort(), "port");
ParamCheckUtil.checkNotNull(req.getGroupId(), "groupId");
return portPoolService.create(req);
}
@@ -92,4 +88,28 @@ public class PortPoolController {
portPoolService.delete(req.getId());
}
@Get
@Mapping("/get-available-port-list")
public List<PortPoolListRes> getAvailablePortList(AvailablePortListReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
return portPoolService.getAvailablePortList(req);
}
@Get
@Mapping("/get-by-group")
public List<PortPoolListRes> portListByGroupId(String groupId) {
return portPoolService.portListByGroupId(groupId);
}
@Put
@Mapping("/update-group")
public PortPoolUpdateGroupRes updateGroup(PortPoolUpdateGroupReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getGroupId(), "groupId");
ParamCheckUtil.checkNotEmpty(req.getPortIdList(), "portIdList");
return portPoolService.updateGroup(req);
}
}
@@ -0,0 +1,15 @@
package fun.asgc.neutrino.proxy.server.controller.req;
import lombok.Data;
/**
* 获取可用端口请求
*/
@Data
public class AvailablePortListReq {
/**
* licenseId
*/
private Integer licenseId;
}
@@ -29,5 +29,6 @@ import lombok.Data;
*/
@Data
public class LicenseFlowReportReq {
private Integer userId;
private Integer licenseId;
}
@@ -31,4 +31,7 @@ import lombok.Data;
@Data
public class LicenseListReq {
private Integer userId;
private Integer isOnline;
private Integer enable;
}
@@ -0,0 +1,27 @@
package fun.asgc.neutrino.proxy.server.controller.req;
import lombok.Data;
/**
* 端口分组创建请求
*
*
*/
@Data
public class PortGroupCreateReq {
/**
* 分组名称
*/
private String name ;
/**
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
*/
private Integer possessorType ;
/**
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
*/
private Integer possessorId ;
}
@@ -0,0 +1,11 @@
package fun.asgc.neutrino.proxy.server.controller.req;
import lombok.Data;
/**
* 删除端口分组请求
*/
@Data
public class PortGroupDeleteReq {
private Integer id;
}
@@ -0,0 +1,27 @@
package fun.asgc.neutrino.proxy.server.controller.req;
import lombok.Data;
/**
* 端口分组查询请求
*
*
*/
@Data
public class PortGroupListReq {
/**
* 分组名称
*/
private String name ;
/**
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
*/
private Integer possessorType ;
/**
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
*/
private Integer possessorId ;
}
@@ -0,0 +1,13 @@
package fun.asgc.neutrino.proxy.server.controller.req;
import lombok.Data;
/**
* 修改端口分组请求
*/
@Data
public class PortGroupUpdateEnableStatusReq {
private Integer id;
private Integer enable;
}
@@ -31,4 +31,6 @@ import lombok.Data;
@Data
public class PortPoolCreateReq {
private Integer port;
private Integer groupId;
}
@@ -0,0 +1,17 @@
package fun.asgc.neutrino.proxy.server.controller.req;
import lombok.Data;
import java.util.List;
/**
* 批量修改端口分组请求
*/
@Data
public class PortPoolUpdateGroupReq {
private String groupId;
private List<Integer> portIdList;
}
@@ -29,5 +29,8 @@ import lombok.Data;
*/
@Data
public class UserFlowReportReq {
/**
* 用户ID
*/
private Integer userId;
}
@@ -0,0 +1,7 @@
package fun.asgc.neutrino.proxy.server.controller.res;
import lombok.Data;
@Data
public class AvailablePortListRes {
}
@@ -22,6 +22,7 @@
package fun.asgc.neutrino.proxy.server.controller.res;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
@@ -29,6 +30,7 @@ import java.util.Date;
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Accessors(chain = true)
@Data
public class LicenseFlowReportRes {
/**
@@ -48,31 +50,27 @@ public class LicenseFlowReportRes {
*/
private String licenseName;
/**
* 写入字节数
* 上行流量字节数
*/
private Long writeBytes;
private Long upFlowBytes;
/**
* 读取字节数
* 下行流量字节数
*/
private Long readBytes;
private Long downFlowBytes;
/**
* 写入流量描述
* 总流量字节数
*/
private String writeFlowStr;
private Long totalFlowBytes;
/**
* 读取流量描述
* 上行流量描述
*/
private String readFlowStr;
private String upFlowDesc;
/**
* 流量描述
* 下行流量描述
*/
private String flowStr;
private String downFlowDesc;
/**
* 报表时间
* 总流量描述
*/
private Date date;
/**
* 创建时间
*/
private Date createTime;
private String totalFlowDesc;
}
@@ -0,0 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res;
public class PortGroupCreateRes {
}
@@ -0,0 +1,50 @@
package fun.asgc.neutrino.proxy.server.controller.res;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.util.Date;
public class PortGroupListRes {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 分组名称
*/
private String name;
/**
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
*/
private Integer possessorType;
/**
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
*/
private Integer possessorId;
/**
* 是否启用(1、启用 2、禁用)
*/
private Integer enable;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 来源
*/
private String possessor;
}
@@ -0,0 +1,7 @@
package fun.asgc.neutrino.proxy.server.controller.res;
import lombok.Data;
@Data
public class PortGroupUpdateEnableStatusRes {
}
@@ -49,4 +49,14 @@ public class PortPoolListRes {
* 更新时间
*/
private Date updateTime;
/**
* 分组类型
*/
private Integer possessorType;
/**
* 分组
*/
private String groupName;
}
@@ -0,0 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res;
public class PortPoolUpdateGroupRes {
}
@@ -22,6 +22,7 @@
package fun.asgc.neutrino.proxy.server.controller.res;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
@@ -29,6 +30,7 @@ import java.util.Date;
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Accessors(chain = true)
@Data
public class UserFlowReportRes {
/**
@@ -40,39 +42,27 @@ public class UserFlowReportRes {
*/
private String userName;
/**
* 历史写入字节数
* 上行流量字节数
*/
private Long historyWriteBytes;
private Long upFlowBytes;
/**
* 历史读取字节数
* 下行流量字节数
*/
private Long historyReadBytes;
private Long downFlowBytes;
/**
* 写入字节数
* 总流量字节数
*/
private Long writeBytes;
private Long totalFlowBytes;
/**
* 读取字节数
* 上行流量描述
*/
private Long readBytes;
private String upFlowDesc;
/**
* 写入流量描述
* 下行流量描述
*/
private String writeFlowStr;
private String downFlowDesc;
/**
* 读取流量描述
* 流量描述
*/
private String readFlowStr;
/**
* 流量描述
*/
private String flowStr;
/**
* 报表时间
*/
private Date date;
/**
* 创建时间
*/
private Date createTime;
private String totalFlowDesc;
}
@@ -0,0 +1,27 @@
package fun.asgc.neutrino.proxy.server.dal;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import fun.asgc.neutrino.proxy.server.controller.req.PortGroupListReq;
import fun.asgc.neutrino.proxy.server.controller.res.PortGroupListRes;
import fun.asgc.neutrino.proxy.server.dal.entity.PortGroupDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
import java.util.List;
@Mapper
public interface PortGroupMapper extends BaseMapper<PortGroupDO> {
List<PortGroupListRes> selectPortGroupListResList(PortGroupListReq res);
default void updateEnableStatus(Integer id, Integer enable, Date now){
this.update(null, Wrappers.lambdaUpdate(PortGroupDO.class)
.eq(PortGroupDO::getId,id)
.set(PortGroupDO::getEnable,enable)
.set(PortGroupDO::getUpdateTime,now)
);
}
}
@@ -24,10 +24,15 @@ package fun.asgc.neutrino.proxy.server.dal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolListReq;
import fun.asgc.neutrino.proxy.server.controller.res.AvailablePortListRes;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolListRes;
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
*
@@ -53,4 +58,15 @@ public interface PortPoolMapper extends BaseMapper<PortPoolDO> {
default PortPoolDO findById(Integer id) {
return this.selectById(id);
}
default List<PortPoolDO> getByGroupId(String groupId){
return this.selectList(
new LambdaQueryWrapper<PortPoolDO>()
.eq(PortPoolDO::getGroupId, groupId)
);
}
List<PortPoolListRes> selectResList(PortPoolListReq req);
List<PortPoolListRes> getAvailablePortList(@Param("licenseId") Integer licenseId,@Param("userId") Integer userId);
}
@@ -0,0 +1,32 @@
package fun.asgc.neutrino.proxy.server.dal;
import fun.asgc.neutrino.proxy.server.controller.req.LicenseFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.req.UserFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.res.LicenseFlowReportRes;
import fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @author: aoshiguchen
* @date: 2023/3/19
*/
@Mapper
public interface ReportMapper {
/**
* 基于用户维度的流量报表
* @param req
* @return
*/
List<UserFlowReportRes> userFlowReportList(@Param("req") UserFlowReportReq req);
/**
* 基于license维度的流量报表
* @param req
* @return
*/
List<LicenseFlowReportRes> licenseFLowReportList(@Param("req")LicenseFlowReportReq req);
}
@@ -0,0 +1,57 @@
package fun.asgc.neutrino.proxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 端口组
*/
@ToString
@Accessors(chain = true)
@Data
@TableName("port_group")
public class PortGroupDO {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 分组名称
*/
private String name;
/**
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
*/
private Integer possessorType;
/**
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
*/
private Integer possessorId;
/**
* 是否启用(1、启用 2、禁用)
*/
private Integer enable;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -42,6 +42,12 @@ import java.util.Date;
public class PortPoolDO {
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 分组id
*/
private Integer groupId;
/**
* 端口
*/
@@ -51,7 +51,10 @@ public class LicenseService implements Lifecycle {
public PageInfo<LicenseListRes> page(PageQuery pageQuery, LicenseListReq req) {
Page<LicenseListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
.orderByAsc(LicenseDO::getId)
.eq(req.getUserId() != null, LicenseDO::getUserId, req.getUserId())
.eq(req.getIsOnline() != null, LicenseDO::getIsOnline, req.getIsOnline())
.eq(req.getEnable() != null, LicenseDO::getEnable, req.getEnable())
.orderByAsc(Arrays.asList(LicenseDO::getUserId, LicenseDO::getId))
);
List<LicenseListRes> respList = mapperFacade.mapAsList(list, LicenseListRes.class);
if (CollectionUtils.isEmpty(list)) {
@@ -0,0 +1,105 @@
package fun.asgc.neutrino.proxy.server.service;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.ServiceException;
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
import fun.asgc.neutrino.proxy.server.constant.Constants;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.controller.req.PortGroupCreateReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortGroupListReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortGroupUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.res.*;
import fun.asgc.neutrino.proxy.server.dal.PortGroupMapper;
import fun.asgc.neutrino.proxy.server.dal.PortPoolMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
import fun.asgc.neutrino.proxy.server.dal.entity.PortGroupDO;
import fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO;
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* 端口分组服务
*/
@Component
public class PortGroupService {
@Inject
private MapperFacade mapperFacade;
@Db
private PortGroupMapper portGroupMapper;
@Db
private PortPoolMapper portPoolMapper;
public PortGroupCreateRes create(PortGroupCreateReq req) {
PortGroupDO portGroupDO = portGroupMapper.selectOne(Wrappers.lambdaQuery(PortGroupDO.class)
.eq(PortGroupDO::getName, req.getName()));
if (Objects.nonNull(portGroupDO)) {
throw ServiceException.create(ExceptionConstant.PORT_GROUP_NAME_ALREADY_EXIST, req.getName());
}
Date now = new Date();
portGroupDO = new PortGroupDO();
portGroupDO.setName(req.getName());
portGroupDO.setPossessorType(req.getPossessorType());
portGroupDO.setPossessorId(req.getPossessorId());
portGroupDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
portGroupDO.setCreateTime(now);
portGroupDO.setUpdateTime(now);
portGroupMapper.insert(portGroupDO);
return new PortGroupCreateRes();
}
public PageInfo<PortGroupListRes> page(PageQuery pageQuery, PortGroupListReq req) {
Page<PortGroupListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<PortGroupListRes> list = portGroupMapper.selectPortGroupListResList(req);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
public List<PortGroupListRes> list(PortGroupListReq req) {
List<PortGroupListRes> list = portGroupMapper.selectPortGroupListResList(req);
return list;
}
public PortGroupUpdateEnableStatusRes updateEnableStatus(PortGroupUpdateEnableStatusReq req) {
PortGroupDO portGroupDO = portGroupMapper.selectById(req.getId());
ParamCheckUtil.checkNotNull(portGroupDO, ExceptionConstant.PORT_GROUP_NAME_DOES_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
ParamCheckUtil.checkExpression(false, ExceptionConstant.NO_PERMISSION_VISIT);
}
portGroupMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
return new PortGroupUpdateEnableStatusRes();
}
public void delete(Integer id) {
if (id == Constants.DEFAULT_PORT_GROUP_ID) {
throw ServiceException.create(ExceptionConstant.DEFAULT_GROUP_FORBID_DELETE);
}
PortGroupDO portGroupDO = portGroupMapper.selectById(id);
//检验分组是否存在
ParamCheckUtil.checkNotNull(portGroupDO, ExceptionConstant.PORT_GROUP_NAME_DOES_NOT_EXIST);
//删除
portGroupMapper.deleteById(id);
//修改绑定此分组的端口到默认分组
portPoolMapper.update(null, Wrappers.lambdaUpdate(PortPoolDO.class)
.eq(PortPoolDO::getGroupId, portGroupDO.getId())
.set(PortPoolDO::getGroupId, Constants.DEFAULT_PORT_GROUP_ID)
.set(PortPoolDO::getUpdateTime, new Date())
);
}
}
@@ -1,16 +1,16 @@
/**
* Copyright (c) 2022 aoshiguchen
* <p>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* <p>
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -22,77 +22,93 @@
package fun.asgc.neutrino.proxy.server.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.ServiceException;
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolCreateReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolListReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolCreateRes;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolListRes;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolUpdateEnableStatusRes;
import fun.asgc.neutrino.proxy.server.controller.req.*;
import fun.asgc.neutrino.proxy.server.controller.res.*;
import fun.asgc.neutrino.proxy.server.dal.PortGroupMapper;
import fun.asgc.neutrino.proxy.server.dal.PortMappingMapper;
import fun.asgc.neutrino.proxy.server.dal.PortPoolMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.PortGroupDO;
import fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO;
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
*
* @author: aoshiguchen
* @date: 2022/8/7
*/
@Component
public class PortPoolService {
@Inject
private MapperFacade mapperFacade;
@Db
private PortPoolMapper portPoolMapper;
@Inject
private VisitorChannelService visitorChannelService;
@Inject
private MapperFacade mapperFacade;
@Db
private PortPoolMapper portPoolMapper;
@Inject
private VisitorChannelService visitorChannelService;
@Db
private PortMappingMapper portMappingMapper;
private PortGroupMapper portGroupMapper;
@Db
private PortMappingMapper portMappingMapper;
public PageInfo<PortPoolListRes> page(PageQuery pageQuery, PortPoolListReq req) {
Page<PortPoolListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<PortPoolDO> list = portPoolMapper.selectList(new LambdaQueryWrapper<PortPoolDO>().orderByAsc(PortPoolDO::getId));
List<PortPoolListRes> respList = mapperFacade.mapAsList(list, PortPoolListRes.class);
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
List<PortPoolListRes> list = portPoolMapper.selectResList(req);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
public List<PortPoolListRes> list(PortPoolListReq req) {
List<PortPoolDO> list = portPoolMapper.selectList(new LambdaQueryWrapper<PortPoolDO>().eq(PortPoolDO::getEnable, EnableStatusEnum.ENABLE.getStatus()));
return mapperFacade.mapAsList(this.filterUsedPorts(list), PortPoolListRes.class);
List<PortPoolDO> list = portPoolMapper.selectList(new LambdaQueryWrapper<PortPoolDO>()
.eq(PortPoolDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
);
return mapperFacade.mapAsList(this.filterUsedPorts(list), PortPoolListRes.class);
}
private List<PortPoolDO> filterUsedPorts(List<PortPoolDO> list) {
//Gets the used ports
List<PortMappingDO> usePorts = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>().orderByAsc(PortMappingDO::getId));
private List<PortPoolDO> filterUsedPorts(List<PortPoolDO> list) {
//Gets the used ports
List<PortMappingDO> usePorts = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>().orderByAsc(PortMappingDO::getId));
List<Integer> serverPorts = usePorts.stream().map(item -> item.getServerPort()).collect(Collectors.toList());
List<Integer> serverPorts = usePorts.stream().map(item -> item.getServerPort()).collect(Collectors.toList());
return list.stream().filter(item -> !serverPorts.contains(item.getPort())).collect(Collectors.toList());
}
return list.stream().filter(item -> !serverPorts.contains(item.getPort())).collect(Collectors.toList());
}
public PortPoolCreateRes create(PortPoolCreateReq req) {
public PortPoolCreateRes create(PortPoolCreateReq req) {
PortPoolDO oldPortPoolDO = portPoolMapper.findByPort(req.getPort());
ParamCheckUtil.checkMustNull(oldPortPoolDO, ExceptionConstant.PORT_CANNOT_REPEAT);
PortGroupDO portGroupDO = portGroupMapper.selectById(req.getGroupId());
ParamCheckUtil.checkNotNull(portGroupDO, ExceptionConstant.PORT_GROUP_NAME_DOES_NOT_EXIST);
Date now = new Date();
portPoolMapper.insert(new PortPoolDO().setPort(req.getPort()).setEnable(EnableStatusEnum.ENABLE.getStatus()).setCreateTime(now).setUpdateTime(now));
portPoolMapper.insert(new PortPoolDO()
.setPort(req.getPort())
.setGroupId(req.getGroupId())
.setEnable(EnableStatusEnum.ENABLE.getStatus())
.setCreateTime(now)
.setUpdateTime(now)
);
// 更新visitorChannel
visitorChannelService.updateVisitorChannelByPortPool(req.getPort(), EnableStatusEnum.ENABLE.getStatus());
@@ -120,4 +136,27 @@ public class PortPoolService {
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), EnableStatusEnum.DISABLE.getStatus());
}
public List<PortPoolListRes> portListByGroupId(String groupId) {
List<PortPoolDO> portPoolDOList = portPoolMapper.getByGroupId(groupId);
List<PortPoolListRes> portPoolListReList = mapperFacade.mapAsList(portPoolDOList, PortPoolListRes.class);
return portPoolListReList;
}
public PortPoolUpdateGroupRes updateGroup(PortPoolUpdateGroupReq req) {
PortGroupDO portGroupDO = portGroupMapper.selectById(req.getGroupId());
if (Objects.isNull(portGroupDO)) {
throw ServiceException.create(ExceptionConstant.PARAMS_INVALID);
}
portPoolMapper.update(null, Wrappers.lambdaUpdate(PortPoolDO.class)
.in(PortPoolDO::getId, req.getPortIdList())
.set(PortPoolDO::getGroupId, req.getGroupId())
.set(PortPoolDO::getUpdateTime, new Date())
);
return new PortPoolUpdateGroupRes();
}
public List<PortPoolListRes> getAvailablePortList(AvailablePortListReq req) {
UserDO user = SystemContextHolder.getUser();
return portPoolMapper.getAvailablePortList(req.getLicenseId(), user.getId());
}
}
@@ -1,13 +1,25 @@
package fun.asgc.neutrino.proxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.collect.Lists;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.controller.req.LicenseFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.req.UserFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.res.JobLogListRes;
import fun.asgc.neutrino.proxy.server.controller.res.LicenseFlowReportRes;
import fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes;
import fun.asgc.neutrino.proxy.server.dal.ReportMapper;
import fun.asgc.neutrino.proxy.server.util.FormatUtil;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.util.List;
/**
* @author: aoshiguchen
@@ -16,6 +28,10 @@ import org.noear.solon.annotation.Component;
@Slf4j
@Component
public class ReportService {
@Inject
private MapperFacade mapperFacade;
@Db
private ReportMapper reportMapper;
/**
* 用户流量报表分页
@@ -24,8 +40,10 @@ public class ReportService {
* @return
*/
public PageInfo<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
// TODO
return null;
Page<UserFlowReportRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<UserFlowReportRes> list = reportMapper.userFlowReportList(req);
fillUserFlowReport(list);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
/**
@@ -35,7 +53,43 @@ public class ReportService {
* @return
*/
public PageInfo<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
// TODO
return null;
Page<LicenseFlowReportRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<LicenseFlowReportRes> list = reportMapper.licenseFLowReportList(req);
fillLicenseFlowReport(list);
return PageInfo.of(list, 25L, pageQuery.getCurrent(), pageQuery.getSize());
}
private void fillUserFlowReport(List<UserFlowReportRes> list) {
if (CollectionUtil.isEmpty(list)) {
return;
}
for (UserFlowReportRes item : list) {
long upFlowBytes = (null == item.getUpFlowBytes()) ? 0 : item.getUpFlowBytes();
long downFlowBytes = (null == item.getDownFlowBytes()) ? 0 : item.getDownFlowBytes();
long totalFlowBytes = upFlowBytes + downFlowBytes;
item.setUpFlowBytes(upFlowBytes);
item.setDownFlowBytes(downFlowBytes);
item.setTotalFlowBytes(totalFlowBytes);
item.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(upFlowBytes));
item.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(downFlowBytes));
item.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(totalFlowBytes));
}
}
private void fillLicenseFlowReport(List<LicenseFlowReportRes> list) {
if (CollectionUtil.isEmpty(list)) {
return;
}
for (LicenseFlowReportRes item : list) {
long upFlowBytes = (null == item.getUpFlowBytes()) ? 0 : item.getUpFlowBytes();
long downFlowBytes = (null == item.getDownFlowBytes()) ? 0 : item.getDownFlowBytes();
long totalFlowBytes = upFlowBytes + downFlowBytes;
item.setUpFlowBytes(upFlowBytes);
item.setDownFlowBytes(downFlowBytes);
item.setTotalFlowBytes(totalFlowBytes);
item.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(upFlowBytes));
item.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(downFlowBytes));
item.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(totalFlowBytes));
}
}
}
@@ -0,0 +1,49 @@
package fun.asgc.neutrino.proxy.server.util;
/**
* @author: aoshiguchen
* @date: 2023/3/19
*/
public class FormatUtil {
private static final String[] SIZE_UNINTS = {"B", "KB", "MB", "GB", "TB"};
private static final int SIZE_SYSTEM = 1024;
/**
* 根据字节数获取大小描述
* 1、小于1024字节的以B为单位
* 2、小于1024KB的以KB为单位
* 3、小于1024M的以MB为单位
* 4、小于1024G的以GB为单位
* 5、其他以TB为单位
* @param byteCount
* @return
*/
public static String getSizeDescByByteCount(long byteCount){
if(byteCount <= 0){
return "0B";
}
double res = byteCount;
int index = 0;
while (index < SIZE_UNINTS.length && res >= SIZE_SYSTEM){
res /= SIZE_SYSTEM;
index++;
}
if(index >= SIZE_UNINTS.length){
index = SIZE_UNINTS.length - 1;
res *= 1024;
}
return trimZero(String.format("%.2f", res)) + SIZE_UNINTS[index];
}
private static String trimZero(String s) {
if (s.indexOf(".") > 0) {
// 去掉多余的0
s = s.replaceAll("0+?$", "");
// 如最后一位是.则去掉
s = s.replaceAll("[.]$", "");
}
return s;
}
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fun.asgc.neutrino.proxy.server.dal.PortGroupMapper">
<select id="selectPortGroupListResList" resultType="fun.asgc.neutrino.proxy.server.controller.res.PortGroupListRes">
SELECT g.*,
CASE
WHEN g.possessor_type = 1 THEN
u.`name`
WHEN g.possessor_type = 2 THEN
l.`name`
ELSE ''
END possessor
FROM port_group g
LEFT JOIN `user` u ON g.possessor_id = u.id
LEFT JOIN license l ON g.possessor_id = l.id
ORDER BY g.id
</select>
</mapper>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fun.asgc.neutrino.proxy.server.dal.PortPoolMapper">
<select id="selectResList" resultType="fun.asgc.neutrino.proxy.server.controller.res.PortPoolListRes">
SELECT p.*,
g.possessor_type,
g.`name` group_name
FROM port_pool p
LEFT JOIN port_group g ON p.group_id = g.id
ORDER BY p.id
</select>
<select id="getAvailablePortList" resultType="fun.asgc.neutrino.proxy.server.controller.res.PortPoolListRes">
SELECT
*
FROM
port_pool
WHERE
`port` NOT IN ( SELECT server_port FROM port_mapping )
AND group_id IN (
SELECT
id
FROM
port_group
WHERE
possessor_type = 0
OR ( possessor_type = 1 AND possessor_id = #{userId,jdbcType=INTEGER} )
OR ( possessor_type = 2 AND possessor_id = #{licenseId,jdbcType=INTEGER} )
)
</select>
</mapper>
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fun.asgc.neutrino.proxy.server.dal.ReportMapper">
<select id="userFlowReportList" resultType="fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes">
SELECT
u.id AS 'userId',
u.name AS 'userName',
SUM(frm.write_bytes + frd.write_bytes + frm2.write_bytes) AS 'upFlowBytes',
SUM(frm.read_bytes + frd.read_bytes + frm2.read_bytes) AS 'downFlowBytes',
SUM(frm.write_bytes) monthWriteBytes,
SUM(frm.read_bytes) monthReadBytes,
SUM(frd.write_bytes) dayWriteBytes,
SUM(frd.read_bytes) dayReadBytes,
SUM(frm2.write_bytes) minuteWriteBytes,
SUM(frm2.read_bytes) minuteReadBytes
FROM `user` u
LEFT JOIN (SELECT user_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_month GROUP BY user_id) frm ON u.id = frm.user_id
LEFT JOIN (SELECT user_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_day
WHERE date >= STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', month(CURDATE()), '-', '01'), '%Y-%m-%d') AND date &lt;= STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', month(CURDATE()), '-', day(CURDATE())), '%Y-%m-%d')
GROUP BY user_id) frd ON u.id = frd.user_id
LEFT JOIN (SELECT user_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_minute
WHERE date >= STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', month(CURDATE()), '-', day(CURDATE())), '%Y-%m-%d') AND date &lt;= STR_TO_DATE(CONCAT(YEAR(CURDATE()),'-',MONTH(CURDATE()),'-',DAY(CURDATE()), ' ',HOUR(CURTIME()),':',MINUTE(CURTIME())), "%Y-%m-%d %H:%i")
GROUP BY user_id) frm2 ON u.id = frm2.user_id
<where>
<if test="req.userId != null">
AND u.id = #{req.userId}
</if>
</where>
GROUP BY u.id
</select>
<select id="licenseFLowReportList" resultType="fun.asgc.neutrino.proxy.server.controller.res.LicenseFlowReportRes">
SELECT
l.id AS 'licenseId',
l.name AS 'licenseName',
u.id AS 'userId',
u.name AS 'userName',
SUM(frm.write_bytes + frd.write_bytes + frm2.write_bytes) AS 'upFlowBytes',
SUM(frm.read_bytes + frd.read_bytes + frm2.read_bytes) AS 'downFlowBytes',
SUM(frm.write_bytes) monthWriteBytes,
SUM(frm.read_bytes) monthReadBytes,
SUM(frd.write_bytes) dayWriteBytes,
SUM(frd.read_bytes) dayReadBytes,
SUM(frm2.write_bytes) minuteWriteBytes,
SUM(frm2.read_bytes) minuteReadBytes
FROM `license` l
LEFT JOIN `user` u ON l.user_id = u.id
LEFT JOIN (SELECT license_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_month GROUP BY license_id) frm ON l.id = frm.license_id
LEFT JOIN (SELECT license_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_day
WHERE date >= STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', month(CURDATE()), '-', '01'), '%Y-%m-%d') AND date &lt;= STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', month(CURDATE()), '-', day(CURDATE())), '%Y-%m-%d')
GROUP BY license_id) frd ON l.id = frd.license_id
LEFT JOIN (SELECT license_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_minute
WHERE date >= STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', month(CURDATE()), '-', day(CURDATE())), '%Y-%m-%d') AND date &lt;= STR_TO_DATE(CONCAT(YEAR(CURDATE()),'-',MONTH(CURDATE()),'-',DAY(CURDATE()), ' ',HOUR(CURTIME()),':',MINUTE(CURTIME())), "%Y-%m-%d %H:%i")
GROUP BY license_id)frm2 ON l.id = frm2.license_id
<where>
<if test="req.userId != null">
AND u.id = #{req.userId}
</if>
</where>
GROUP BY l.id
</select>
</mapper>
@@ -29,6 +29,7 @@ CREATE TABLE IF NOT EXISTS `user_token` (
#
CREATE TABLE IF NOT EXISTS `port_pool` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` int NOT NULL DEFAULT 1 COMMENT '分组ID',
`port` int NOT NULL COMMENT '端口',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
@@ -37,6 +38,18 @@ CREATE TABLE IF NOT EXISTS `port_pool` (
KEY `I_port_pool_port` (`port`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
#
CREATE TABLE IF NOT EXISTS `port_group` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(255) NOT NULL COMMENT '分组名称',
`possessor_type` int NOT NULL DEFAULT '0' COMMENT '所有者类型 (0、全局共享 1、用户所有 2License所有) ',
`possessor_id` int NOT NULL DEFAULT '-1' COMMENT '所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='端口分组';
##########################################################
#license表
CREATE TABLE IF NOT EXISTS `license` (
@@ -66,7 +79,6 @@ CREATE TABLE IF NOT EXISTS `port_mapping` (
PRIMARY KEY (`id`),
KEY `I_port_mapping_server_port` (`server_port`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
##########################################################
#
CREATE TABLE IF NOT EXISTS `user_login_record` (
@@ -0,0 +1,3 @@
#port_group
INSERT INTO `port_group`(`id`,`name`,`possessor_type`,`possessor_id`,`enable`,`create_time`,`update_time`) VALUES
(1, '全局(默认)', 0, -1, 1, now(), now());
@@ -1,41 +1,41 @@
#
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(1, 9101, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(2, 9102, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(3, 9103, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(4, 9104, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(5, 9105, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(6, 9106, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(7, 9107, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(8, 9108, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(9, 9109, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(10, 9110, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(11, 9111, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(12, 9112, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(13, 9113, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(14, 9114, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(15, 9115, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(16, 9116, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(17, 9117, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(18, 9118, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(19, 9119, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(20, 9120, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(3, 1, 9103, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(4, 1, 9104, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(5, 1, 9105, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(6, 1, 9106, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(7, 1, 9107, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(8, 1, 9108, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(9, 1, 9109, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(10, 1, 9110, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(11, 1, 9111, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(12, 1, 9112, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(13, 1, 9113, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(14, 1, 9114, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(15, 1, 9115, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(16, 1, 9116, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(17, 1, 9117, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(18, 1, 9118, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(19, 1, 9119, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(20, 1, 9120, 1, now(), now());
@@ -0,0 +1,13 @@
ALTER TABLE port_pool ADD group_id INT NOT NULL DEFAULT 1 COMMENT "分组ID";
#
CREATE TABLE IF NOT EXISTS `port_group` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(255) NOT NULL COMMENT '分组名称',
`possessor_type` int NOT NULL DEFAULT '0' COMMENT '所有者类型 (0、全局共享 1、用户所有 2License所有) ',
`possessor_id` int NOT NULL DEFAULT '-1' COMMENT '所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='端口分组';
@@ -27,6 +27,7 @@ CREATE INDEX IF NOT EXISTS I_user_token_expiration_time ON user_token(expiration
#
CREATE TABLE IF NOT EXISTS `port_pool` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`group_id` INTEGER NOT NULL DEFAULT 1,
`port` INTEGER NOT NULL,
`enable` INTEGER(2) NOT NULL,
`update_time` INTEGER(20) NOT NULL,
@@ -34,6 +35,16 @@ CREATE TABLE IF NOT EXISTS `port_pool` (
);
CREATE UNIQUE INDEX IF NOT EXISTS I_port_pool_port ON port_pool (port ASC);
#
CREATE TABLE IF NOT EXISTS `port_group` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`name` VARCHAR(255) NOT NULL,
`possessor_type` INTEGER NOT NULL DEFAULT '0',
`possessor_id` INTEGER NOT NULL DEFAULT '-1',
`enable` INTEGER NOT NULL,
`create_time` datetime(3) NOT NULL,
`update_time` datetime(3) NOT NULL
);
##########################################################
#license表
CREATE TABLE IF NOT EXISTS `license` (
@@ -0,0 +1,3 @@
#port_group
INSERT INTO `port_group`(`id`,`name`,`possessor_type`,`possessor_id`,`enable`,`create_time`,`update_time`) VALUES
(1, '全局(默认)', 0, -1, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
@@ -1,41 +1,41 @@
#
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(1, 9101, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(2, 9102, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(3, 9103, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(4, 9104, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(5, 9105, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(6, 9106, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(7, 9107, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(8, 9108, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(9, 9109, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(10, 9110, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(11, 9111, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(12, 9112, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(13, 9113, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(14, 9114, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(15, 9115, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(16, 9116, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(17, 9117, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(18, 9118, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(19, 9119, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(20, 9120, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(3, 1, 9103, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(4, 1, 9104, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(5, 1, 9105, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(6, 1, 9106, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(7, 1, 9107, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(8, 1, 9108, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(9, 1, 9109, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(10, 1, 9110, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(11, 1, 9111, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(12, 1, 9112, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(13, 1, 9113, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(14, 1, 9114, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(15, 1, 9115, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(16, 1, 9116, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(17, 1, 9117, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(18, 1, 9118, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(19, 1, 9119, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(20, 1, 9120, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
@@ -0,0 +1,12 @@
ALTER TABLE port_pool ADD group_id INTEGER NOT NULL DEFAULT 1;
#
CREATE TABLE IF NOT EXISTS `port_group` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`name` VARCHAR(255) NOT NULL,
`possessor_type` INTEGER NOT NULL DEFAULT '0',
`possessor_id` INTEGER NOT NULL DEFAULT '-1',
`enable` INTEGER NOT NULL,
`create_time` datetime(3) NOT NULL,
`update_time` datetime(3) NOT NULL
);
+19
View File
@@ -0,0 +1,19 @@
# npm
package-lock.json
node_modules
yarn-error.log
# vscode
.vscode
#yarn
yarn.lock
# vuepress
docs/.vuepress/dist
# 百度链接推送
urls.txt
# mac
.DS_Store
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019-present gaoyi(Evan) Xu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+10
View File
@@ -0,0 +1,10 @@
<p align="center"><a href="https://xugaoyi.com/" target="_blank" rel="noopener noreferrer"><img width="180" src="https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200409124835.png" alt="logo"></a></p>
<h2 align="center">vuepress-theme-vdoing</h2>
[在线文档(国内源)](https://doc.xugaoyi.com/)
[主题仓库](https://github.com/xugaoyi/vuepress-theme-vdoing)
[本仓库的gitee镜像](https://gitee.com/xugaoyi/vuepress-theme-vdoing-doc)
@@ -0,0 +1,246 @@
const baiduCode = require('./config/baiduCode.js'); // 百度统计hm码
const htmlModules = require('./config/htmlModules.js');
module.exports = {
theme: 'vdoing', // 使用依赖包主题
// theme: require.resolve('../../vdoing'), // 使用本地主题 (先将vdoing主题文件下载到本地:https://github.com/xugaoyi/vuepress-theme-vdoing)
title: "中微子代理",
description: '一个基于 netty 的、开源的 java 内网穿透项目',
// base: '/', // 默认'/'。如果你想将你的网站部署到如 https://foo.github.io/bar/,那么 base 应该被设置成 "/bar/",(否则页面将失去样式等文件)
head: [ // 注入到页面<head> 中的标签,格式[tagName, { attrName: attrValue }, innerHTML?]
['link', {rel: 'icon', href: '/img/favicon.ico'}], //favicons,资源放在public文件夹
['meta', {name: 'keywords', content: 'vuepress,theme,blog,vdoing'}],
['meta', {name: 'theme-color', content: '#11a8cd'}], // 移动浏览器主题颜色
// ['meta', { name: 'wwads-cn-verify', content: '6c4b761a28b734fe93831e3fb400ce87' }], // 广告相关,你可以去掉
// ['script', { src: 'https://cdn.wwads.cn/js/makemoney.js', type: 'text/javascript' }], // 广告相关,你可以去掉
],
// 主题配置
themeConfig: {
// nav: [
// { text: '首页', link: '/' },
// {
// text: '指南', link: '/pages/a2f161/', items: [
// { text: '主题初衷与诞生', link: '/pages/52d5c3/' },
// { text: '介绍', link: '/pages/a2f161/' },
// { text: '快速上手', link: '/pages/793dcb/' },
// { text: '目录结构', link: '/pages/2f674a/' },
// { text: '核心配置和约定', link: '/pages/33d574/' },
// { text: '自动生成front matter', link: '/pages/088c16/' },
// { text: 'Markdown 容器', link: '/pages/d0d7eb/' },
// { text: 'Markdown 中使用组件', link: '/pages/197691/' },
// {
// text: '相关文章', items: [
// { text: '使目录栏支持h2~h6标题', link: '/pages/8dfab5/' },
// { text: '如何让你的笔记更有表现力', link: '/pages/dd027d/' },
// { text: '批量操作front matter工具', link: '/pages/2b8e22/' },
// { text: '部署', link: '/pages/0fc1d2/' },
// { text: '关于写文章和H1标题', link: '/pages/9ae0bd/' },
// { text: '关于博客搭建与管理', link: '/pages/26997d/' },
// { text: '在线编辑和新增文章的方法', link: '/pages/c5a54d/' },
// ]
// }
// ]
// },
// {
// text: '配置', link: '/pages/a20ce8/', items: [
// { text: '主题配置', link: '/pages/a20ce8/' },
// { text: '首页配置', link: '/pages/f14bdb/' },
// { text: 'front matter配置', link: '/pages/3216b0/' },
// { text: '目录页配置', link: '/pages/54651a/' },
// { text: '添加摘要', link: '/pages/1cc523/' },
// { text: '修改主题颜色和样式', link: '/pages/f51918/' },
// { text: '评论栏', link: '/pages/ce175c/' },
// ]
// },
// { text: '资源', link: '/pages/db78e2/' },
// { text: '案例', link: '/pages/5d571c/' },
// { text: '问答', link: '/pages/9cc27d/' },
// { text: '赞助', link: '/pages/1b12ed/' },
// ],
nav: [
{text: '首页', link: '/'},
{
text: '快速使用', link: '/pages/793dcb/', items: [
{text: '快速上手', link: '/pages/793dcb/'},
{text: '目录结构', link: '/pages/2f674a/'},
{text: 'Markdown 容器', link: '/pages/d0d7eb/'},
{text: 'Markdown 中使用组件', link: '/pages/197691/'},
{
text: '相关文章', items: [
{text: '使目录栏支持h2~h6标题', link: '/pages/8dfab5/'},
{text: '如何让你的笔记更有表现力', link: '/pages/dd027d/'},
{text: '批量操作front matter工具', link: '/pages/2b8e22/'},
{text: '部署', link: '/pages/0fc1d2/'},
{text: '关于写文章和H1标题', link: '/pages/9ae0bd/'},
{text: '关于博客搭建与管理', link: '/pages/26997d/'},
{text: '在线编辑和新增文章的方法', link: '/pages/c5a54d/'},
]
}
]
},
{
text: '常见问题', link: '/pages/a20ce8/', items: [
{text: '主题配置', link: '/pages/a20ce8/'},
{text: '首页配置', link: '/pages/f14bdb/'},
{text: 'front matter配置', link: '/pages/3216b0/'},
{text: '目录页配置', link: '/pages/54651a/'},
{text: '添加摘要', link: '/pages/1cc523/'},
{text: '修改主题颜色和样式', link: '/pages/f51918/'},
{text: '评论栏', link: '/pages/ce175c/'},
]
},
{text: '演示', link: '/pages/db78e2/'},
{text: '案例', link: '/pages/5d571c/'},
{text: '最近更新', link: '/pages/9cc27d/'},
{text: '关于我们', link: '/pages/1b12ed/'},
],
sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题)
logo: '/img/logo.png', // 导航栏logo
repo: 'aoshiguchen/neutrino-proxy', // 导航栏右侧生成Github链接
// repo: 'https://gitee.com/dromara/neutrino-proxy', // 导航栏右侧生成Github链接
searchMaxSuggestions: 10, // 搜索结果显示最大数
lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间)
// docsDir: 'docs', // 编辑的文件夹
// editLinks: true, // 编辑链接
// editLinkText: '编辑',
// 以下配置是Vdoing主题改动的和新增的配置
sidebar: {mode: 'structuring', collapsable: false}, // 侧边栏 'structuring' | { mode: 'structuring', collapsable: Boolean} | 'auto' | 自定义 温馨提示:目录页数据依赖于结构化的侧边栏数据,如果你不设置为'structuring',将无法使用目录页
// sidebarOpen: false, // 初始状态是否打开侧边栏,默认true
updateBar: { // 最近更新栏
showToArticle: false, // 显示到文章页底部,默认true
// moreArticle: '/archives' // “更多文章”跳转的页面,默认'/archives'
},
// titleBadge: false, // 文章标题前的图标是否显示,默认true
// titleBadgeIcons: [ // 文章标题前图标的地址,默认主题内置图标
// '图标地址1',
// '图标地址2'
// ],
pageStyle: 'line', // 页面风格,可选值:'card'卡片 | 'line' 线(未设置bodyBgImg时才生效), 默认'card'。 说明:card时背景显示灰色衬托出卡片样式,line时背景显示纯色,并且部分模块带线条边框
// contentBgStyle: 1,
category: false, // 是否打开分类功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含分类字段 2.页面中显示与分类相关的信息和模块 3.自动生成分类页面(在@pages文件夹)。如关闭,则反之。
tag: false, // 是否打开标签功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含标签字段 2.页面中显示与标签相关的信息和模块 3.自动生成标签页面(在@pages文件夹)。如关闭,则反之。
// archive: false, // 是否打开归档功能,默认true。 如打开,会做的事情有:1.自动生成归档页面(在@pages文件夹)。如关闭,则反之。
author: { // 文章默认的作者信息,可在md文件中单独配置此信息 String | {name: String, href: String}
name: 'Evan Xu', // 必需
href: 'https://github.com/xugaoyi' // 可选的
},
social: { // 社交图标,显示于博主信息栏和页脚栏
// iconfontCssFile: '//at.alicdn.com/t/font_1678482_u4nrnp8xp6g.css', // 可选,阿里图标库在线css文件地址,对于主题没有的图标可自由添加
icons: [
{
iconClass: 'icon-youjian',
title: '发邮件',
link: 'mailto:[email protected]'
},
{
iconClass: 'icon-gitee',
title: 'Gitee',
link: 'https://gitee.com/dromara/neutrino-proxy'
},
{
iconClass: 'icon-github',
title: 'Github',
link: 'https://github.com/aoshiguchen/neutrino-proxy'
}
]
},
footer: { // 页脚信息
createYear: 2023, // 博客创建年份
copyrightInfo: '傲世孤尘 | MIT License', // 博客版权信息,支持a标签
},
htmlModules,
},
// 插件
plugins: [
// [require('./plugins/love-me'), { // 鼠标点击爱心特效
// color: '#11a8cd', // 爱心颜色,默认随机色
// excludeClassName: 'theme-vdoing-content' // 要排除元素的class, 默认空''
// }],
['fulltext-search'], // 全文搜索
// ['thirdparty-search', { // 可以添加第三方搜索链接的搜索框(原官方搜索框的参数仍可用)
// thirdparty: [ // 可选,默认 []
// {
// title: '在GitHub中搜索',
// frontUrl: 'https://github.com/search?q=', // 搜索链接的前面部分
// behindUrl: '' // 搜索链接的后面部分,可选,默认 ''
// },
// {
// title: '在npm中搜索',
// frontUrl: 'https://www.npmjs.com/search?q=',
// },
// {
// title: '在Bing中搜索',
// frontUrl: 'https://cn.bing.com/search?q='
// }
// ]
// }],
[
'vuepress-plugin-baidu-tongji', // 百度统计
{
hm: baiduCode || '01293bffa6c3962016c08ba685c79d78'
}
],
['one-click-copy', { // 代码块复制按钮
copySelector: ['div[class*="language-"] pre', 'div[class*="aside-code"] aside'], // String or Array
copyMessage: '复制成功', // default is 'Copy successfully and then paste it for use.'
duration: 1000, // prompt message display time.
showInMobile: false // whether to display on the mobile side, default: false.
}],
['demo-block', { // demo演示模块 https://github.com/xiguaxigua/vuepress-plugin-demo-block
settings: {
// jsLib: ['http://xxx'], // 在线示例(jsfiddle, codepen)中的js依赖
// cssLib: ['http://xxx'], // 在线示例中的css依赖
// vue: 'https://fastly.jsdelivr.net/npm/vue/dist/vue.min.js', // 在线示例中的vue依赖
jsfiddle: false, // 是否显示 jsfiddle 链接
codepen: true, // 是否显示 codepen 链接
horizontal: false // 是否展示为横向样式
}
}],
[
'vuepress-plugin-zooming', // 放大图片
{
selector: '.theme-vdoing-content img:not(.no-zoom)',
options: {
bgColor: 'rgba(0,0,0,0.6)'
},
},
],
[
'@vuepress/last-updated', // "上次更新"时间格式
{
transformer: (timestamp, lang) => {
const dayjs = require('dayjs') // https://day.js.org/
return dayjs(timestamp).format('YYYY/MM/DD, HH:mm:ss')
},
}
]
],
markdown: {
// lineNumbers: true,
extractHeaders: ['h2', 'h3', 'h4', 'h5', 'h6'], // 提取标题到侧边栏的级别,默认['h2', 'h3']
},
// 监听文件变化并重新构建
extraWatchFiles: [
'.vuepress/config.js',
'.vuepress/config/htmlModules.js',
]
}
@@ -0,0 +1 @@
module.exports = '';
@@ -0,0 +1,75 @@
/** 插入自定义html模块 (可用于插入广告模块等)
* {
* homeSidebarB: htmlString, 首页侧边栏底部
*
* sidebarT: htmlString, 全局左侧边栏顶部
* sidebarB: htmlString, 全局左侧边栏底部
*
* pageT: htmlString, 全局页面顶部
* pageB: htmlString, 全局页面底部
* pageTshowMode: string, 页面顶部-显示方式:未配置默认全局;'article' => 仅文章页①; 'custom' => 仅自定义页①
* pageBshowMode: string, 页面底部-显示方式:未配置默认全局;'article' => 仅文章页①; 'custom' => 仅自定义页①
*
* windowLB: htmlString, 全局左下角②
* windowRB: htmlString, 全局右下角②
* }
*
* ①注:在.md文件front matter配置`article: false`的页面是自定义页,未配置的默认是文章页(首页除外)。
* ②注:windowLB 和 windowRB1.展示区块最大宽高200px*400px。2.请给自定义元素定一个不超过200px*400px的宽高。3.在屏幕宽度小于960px时无论如何都不会显示。
*/
module.exports = {
// 万维广告
pageT: `
<div class="wwads-cn wwads-horizontal page-wwads" data-id="136"></div>
<style>
.page-wwads{
width:100%!important;
min-height: 0;
margin: 0;
}
.page-wwads .wwads-img img{
width:80px!important;
}
.page-wwads .wwads-poweredby{
width: 40px;
position: absolute;
right: 25px;
bottom: 3px;
}
.wwads-content .wwads-text, .page-wwads .wwads-text{
height: 100%;
padding-top: 5px;
display: block;
}
</style>
`,
// 赞助商广告
sidebarT: `<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/431669861564_.2470ykdcpbds.jpg" alt="npm" class="no-zoom" style="width: 100%;border-radius: 2px;"></a>`,
// windowRB: `
// <div class="wwads-cn wwads-vertical windowRB" data-id="136" style="max-width:160px;
// min-width: auto;min-height:auto;"></div>
// <style>
// .windowRB{ padding: 0;}
// .windowRB .wwads-img{margin-top: 10px;}
// .windowRB .wwads-content{margin: 0 10px 10px 10px;}
// .custom-html-window-rb .close-but{
// display: none;
// }
// </style>
// `
}
// module.exports = {
// homeSidebarB: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
// sidebarT: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
// sidebarB: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
// pageT: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
// pageB: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
// windowLB: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
// windowRB: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
// }
@@ -0,0 +1,12 @@
const path= require('path');
const LoveMyPlugin = (options={}) => ({
define () {
const COLOR = options.color || "rgb(" + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + ")"
const EXCLUDECLASS = options.excludeClassName || ''
return {COLOR, EXCLUDECLASS}
},
enhanceAppFiles: [
path.resolve(__dirname, 'love-me.js')
]
});
module.exports = LoveMyPlugin;
@@ -0,0 +1,62 @@
export default () => {
if (typeof window !== "undefined") {
(function(e, t, a) {
function r() {
for (var e = 0; e < s.length; e++) s[e].alpha <= 0 ? (t.body.removeChild(s[e].el), s.splice(e, 1)) : (s[e].y--, s[e].scale += .004, s[e].alpha -= .013, s[e].el.style.cssText = "left:" + s[e].x + "px;top:" + s[e].y + "px;opacity:" + s[e].alpha + ";transform:scale(" + s[e].scale + "," + s[e].scale + ") rotate(45deg);background:" + s[e].color + ";z-index:99999");
requestAnimationFrame(r)
}
function n() {
var t = "function" == typeof e.onclick && e.onclick;
e.onclick = function(e) {
// 过滤指定元素
let mark = true;
EXCLUDECLASS && e.path && e.path.forEach((item) =>{
if(item.nodeType === 1) {
typeof item.className === 'string' && item.className.indexOf(EXCLUDECLASS) > -1 ? mark = false : ''
}
})
if(mark) {
t && t(),
o(e)
}
}
}
function o(e) {
var a = t.createElement("div");
a.className = "heart",
s.push({
el: a,
x: e.clientX - 5,
y: e.clientY - 5,
scale: 1,
alpha: 1,
color: COLOR
}),
t.body.appendChild(a)
}
function i(e) {
var a = t.createElement("style");
a.type = "text/css";
try {
a.appendChild(t.createTextNode(e))
} catch(t) {
a.styleSheet.cssText = e
}
t.getElementsByTagName("head")[0].appendChild(a)
}
// function c() {
// return "rgb(" + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + ")"
// }
var s = [];
e.requestAnimationFrame = e.requestAnimationFrame || e.webkitRequestAnimationFrame || e.mozRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame ||
function(e) {
setTimeout(e, 1e3 / 60)
},
i(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"),
n(),
r()
})(window, document)
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 420">
<defs>
<style>
.cls-1 {
fill: #ff9045;
}
.cls-2 {
fill: #0079dd;
}
</style>
</defs>
<g>
<circle class="cls-1" cx="240" cy="94.5" r="27.21"/>
<path class="cls-1" d="M257.97,137.7h-36.11c-9.1,0-16.48,7.38-16.48,16.48v184.01c0,8.02,6.5,14.52,14.52,14.52h.14c8.02,0,14.52-6.5,14.52-14.52v-107.89c5.91,0,10.7,4.79,10.7,10.7v32.75c0,8.02,6.5,14.52,14.52,14.52h.14c8.02,0,14.52-6.5,14.52-14.52v-42.24s0-77.34,0-77.34c0-9.1-7.38-16.48-16.48-16.48Z"/>
</g>
<path class="cls-2" d="M183.93,221.1h-3.95c-6.13,0-11.1-4.97-11.1-11.1s4.97-11.1,11.1-11.1h3.95s.08,0,.13,0v-51.31c0-9.51-4.63-18.43-12.4-23.92L51.43,38.95c-9.69-6.83-23.06,.1-23.06,11.96V369.09c0,11.86,13.37,18.79,23.06,11.96l120.22-84.73c7.78-5.48,12.4-14.4,12.4-23.92v-51.31s-.08,0-.13,0Z"/>
<path class="cls-2" d="M428.57,38.95l-120.22,84.73c-7.78,5.48-12.4,14.4-12.4,23.92v51.31s.08,0,.13,0h3.95c6.13,0,11.1,4.97,11.1,11.1s-4.97,11.1-11.1,11.1h-3.95s-.08,0-.13,0v51.31c0,9.51,4.63,18.44,12.4,23.92l120.22,84.73c9.69,6.83,23.06-.1,23.06-11.96V50.91c0-11.86-13.37-18.79-23.06-11.96Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@@ -0,0 +1,126 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
>
<meta
http-equiv="X-UA-Compatible"
content="ie=edge"
>
<title>Markmap</title>
<style>
* {
margin: 0;
padding: 0;
}
#mindmap {
display: block;
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<svg id="mindmap"></svg>
<script src="https://fastly.jsdelivr.net/npm/d3@5"></script>
<script src="https://fastly.jsdelivr.net/npm/[email protected]/dist/browser/view.min.js"></script>
<script>
((a, t, e, n) => {
const {
Markmap: s,
loadPlugins: o
} = window.markmap;
(t ? t(o, e, n) : Promise.resolve()).then(() => {
window.mm = s.create("svg#mindmap", null, a)
})
})({
"t": "heading",
"d": 1,
"p": {},
"v": "markmap-lib",
"c": [{
"t": "heading",
"d": 2,
"p": {},
"v": "Links",
"c": [{
"t": "list_item",
"d": 3,
"p": {},
"v": "<a href=\"https://markmap.js.org/\" target=\"_blank\" rel=\"noopener noreferrer\">https://markmap.js.org/</a>"
}, {
"t": "list_item",
"d": 3,
"p": {},
"v": "<a href=\"https://github.com/gera2ld/markmap-lib\" title=\"\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub</a>"
}]
}, {
"t": "heading",
"d": 2,
"p": {},
"v": "Related",
"c": [{
"t": "list_item",
"d": 3,
"p": {},
"v": "<a href=\"https://github.com/gera2ld/coc-markmap\" title=\"\" target=\"_blank\" rel=\"noopener noreferrer\">coc-markmap</a>"
}, {
"t": "list_item",
"d": 3,
"p": {},
"v": "<a href=\"https://github.com/gera2ld/gatsby-remark-markmap\" title=\"\" target=\"_blank\" rel=\"noopener noreferrer\">gatsby-remark-markmap</a>"
}]
}, {
"t": "heading",
"d": 2,
"p": {},
"v": "Features",
"c": [{
"t": "list_item",
"d": 3,
"p": {},
"v": "links"
}, {
"t": "list_item",
"d": 3,
"p": {},
"v": "<strong>inline</strong> <del>text</del> <em>styles</em>"
}, {
"t": "list_item",
"d": 3,
"p": {},
"v": "multiline<br/>text"
}, {
"t": "list_item",
"d": 3,
"p": {},
"v": "<code>inline code</code>"
}, {
"t": "list_item",
"d": 3,
"p": {},
"v": "<pre><code class=\"language-js\">console.log('code block');\n</code></pre>"
}, {
"t": "list_item",
"d": 3,
"p": {},
"v": "MathJax - <code>\\(x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}\\)</code>"
}]
}]
}, (a, t, e) => a(t, e), ["mathJax", "prism"], {
"mathJax": true,
"prism": true
})
</script>
</body>
</html>
@@ -0,0 +1,93 @@
// .home-wrapper .banner .banner-conent .hero h1{
// font-size 2.8rem!important
// }
// //
// table
// width auto
// .page >*:not(.footer),.card-box
// box-shadow: none!important
// .page
// @media (min-width $contentWidth + 80)
// padding-top $navbarHeight!important
// .home-wrapper .banner .banner-conent
// padding 0 2.9rem
// box-sizing border-box
// .home-wrapper .banner .slide-banner .slide-banner-wrapper .slide-item a
// h2
// margin-top 2rem
// font-size 1.2rem!important
// p
// padding 0 1rem
//
.gt-container
.gt-ico-tip
&::after
content: '( Win + . ) or ( + + ) open Emoji'
color: #999
.gt-meta
border-color var(--borderColor)!important
.gt-comments-null
color var(--textColor)
opacity .5
.gt-header-textarea
color var(--textColor)
background rgba(180,180,180,0.1)!important
.gt-btn
border-color $accentColor!important
background-color $accentColor!important
.gt-btn-preview
background-color rgba(255,255,255,0)!important
color $accentColor!important
a
color $accentColor!important
.gt-svg svg
fill $accentColor!important
.gt-comment-content,.gt-comment-admin .gt-comment-content
background-color rgba(150,150,150,0.1)!important
&:hover
box-shadow 0 0 25px rgba(150,150,150,.5)!important
.gt-comment-body
color var(--textColor)!important
// qq
.qq
position: relative;
.qq::after
content: "";
background: $accentColor;
color:#fff;
padding: 0 5px;
border-radius: 10px;
font-size:12px;
position: absolute;
top: -4px;
right: -35px;
transform:scale(0.85);
// demo
body .vuepress-plugin-demo-block__wrapper
&,.vuepress-plugin-demo-block__display
border-color rgba(160,160,160,.3)
.vuepress-plugin-demo-block__footer:hover
.vuepress-plugin-demo-block__expand::before
border-top-color: $accentColor !important;
border-bottom-color: $accentColor !important;
svg
fill: $accentColor !important;
//
.suggestions
overflow: auto
max-height: calc(100vh - 6rem)
@media (max-width: 719px) {
width: 90vw;
min-width: 90vw!important;
margin-right: -20px;
}
.highlight
color: $accentColor
font-weight: bold
@@ -0,0 +1,62 @@
// vdoing使
//***vdoing-***//
// //
// $bannerTextColor = #fff // 首页banner区()
// $accentColor = #11A8CD
// $arrowBgColor = #ccc
// $badgeTipColor = #42b983
// $badgeWarningColor = darken(#ffe564, 35%)
// $badgeErrorColor = #DA5961
// //
// $navbarHeight = 3.6rem
// $sidebarWidth = 18rem
// $contentWidth = 860px
// $homePageWidth = 1100px
// $rightMenuWidth = 230px //
// //
// $lineNumbersWrapperWidth = 2.5rem
//
// .theme-mode-light
// --bodyBg: rgba(255,255,255,1)
// --mainBg: rgba(255,255,255,1)
// --sidebarBg: rgba(255,255,255,.8)
// --blurBg: rgba(255,255,255,.9)
// // --textColor: #004050
// --textLightenColor: #0085AD
// --borderColor: rgba(0,0,0,.15)
// --codeBg: #f6f6f6
// --codeColor: #525252
// codeThemeLight()
// //
// .theme-mode-dark
// --bodyBg: rgba(30,30,34,1)
// --mainBg: rgba(30,30,34,1)
// --sidebarBg: rgba(30,30,34,.8)
// --blurBg: rgba(30,30,34,.8)
// --textColor: rgb(140,140,150)
// --textLightenColor: #0085AD
// --borderColor: #2C2C3A
// --codeBg: #252526
// --codeColor: #fff
// codeThemeDark()
// //
// .theme-mode-read
// --bodyBg: rgba(245,245,213,1)
// --mainBg: rgba(245,245,213,1)
// --sidebarBg: rgba(245,245,213,.8)
// --blurBg: rgba(245,245,213,.9)
// --textColor: #004050
// --textLightenColor: #0085AD
// --borderColor: rgba(0,0,0,.15)
// --codeBg: #282c34
// --codeColor: #fff
// codeThemeDark()
@@ -0,0 +1,110 @@
---
title: 快速上手
date: 2020-05-11 13:54:40
permalink: /pages/793dcb
article: false
---
## 安装和启动
<code-group>
<code-block title="知识库兼博客风格预设配置" active>
```bash
# clone the project
git clone https://github.com/xugaoyi/vuepress-theme-vdoing.git
# enter the project directory
cd vuepress-theme-vdoing
# install dependency 注意:如安装不成功请关闭淘宝源。
npm install # or yarn install
# develop
npm run dev # or yarn dev
```
</code-block>
<code-block title="文档风格预设配置">
```bash
# clone the project
git clone https://github.com/xugaoyi/vuepress-theme-vdoing-doc.git
# enter the project directory
cd vuepress-theme-vdoing-doc
# install dependency 注意:如安装不成功请关闭淘宝源。
npm install # or yarn install
# develop
npm run dev # or yarn dev
```
> 仓库地址: <https://github.com/xugaoyi/vuepress-theme-vdoing-doc>
</code-block>
<code-block title="简洁模板预设配置(社区提供)">
```bash
# clone the project
git clone https://github.com/u2sb/vuepress-theme-vdoing-template.git
# enter the project directory
cd vuepress-theme-vdoing-template
# install dependency 注意:如安装不成功请关闭淘宝源。
npm install # or yarn install
# develop
npm run dev # or yarn dev
```
> 仓库地址: <https://github.com/u2sb/vuepress-theme-vdoing-template>
</code-block>
</code-group>
::: warning
Node请使用`v14.17.x`或以上版本
:::
## 使用Vdoing主题
1. 安装最新的Vdoing主题包:
```sh
npm install vuepress-theme-vdoing -D
```
2.`.vuepress/config.js`中配置使用主题:
```js
// config.js
module.exports = {
theme: 'vdoing'
}
```
::: tip
1. 不建议在原默认vuepress项目上单独安装使用本主题包,而是clone我的整个项目再替换你自己的内容即可。
2. 修改`config.js`配置后需要重新启动项目才会生效。
3. 更多关于项目上手的问题,请查阅 [问答](/pages/9cc27d/)。
:::
## 版本升级
主题的版本会不定期更新,你只需更新npm主题包即可:
```sh
npm update vuepress-theme-vdoing
```
::: tip
1. 如更新后没起作用或报错,尝试把`node_modules`文件夹删除再`npm i`重新安装。
2. 在.vuepress/config.js中,设置`theme: 'vdoing'`才是使用npm主题依赖包:
```js
// config.js
module.exports = {
theme: 'vdoing', // npm主题依赖包
// theme: require.resolve('../../vdoing'), // 使用本地主题包
}
```
:::
@@ -0,0 +1,68 @@
---
title: 目录结构
date: 2020-05-11 13:54:56
permalink: /pages/2f674a
article: false
---
```
├── data.db (sqlite数据库文件。若未配置mysql,默认使用sqlite,项目首次启动会自动初始化sqlite数据库。)
├── docs (项目相关的一些文档)
├── ├── Aop.MD (框架层Aop机制、使用说明)
├── └── Channel.MD (内网穿透实现原理、代理实现流程说明)
├── lib (项目开启了将自动生成的类保存到本地后,运行过程中动态生成的类自动保存到此处,方便学习、调试)
├── neutrino-core (一套手写的基于netty的框架,相当于简易版的SpringBoot + Mybatis + xxljob,计划后期分离为单独开源项目维护)
├── neutrino-proxy-admin (基于vue-element-admin开发的一个管理系统,用于可视化操作端口映射、代理数据实时监控)
├── neutrino-proxy-client (基于netty的代理客户端,用于和服务端交互、转发内网数据)
├── neutrino-proxy-core (代理相关的公共代码(协议、常量))
├── neutrino-proxy-server (基于netty的代理服务端,用于和客户段交互,将客户端转发的内网数据转发至外网端口)
└── todolist.MD (近期的开发计划)
```
<!--
* `docs` 文件夹名称请不要修改
* `docs/.vuepress` 用于存放全局的配置、样式、静态资源等,同官方,查看 [详情](https://vuepress.vuejs.org/zh/guide/directory-structure.html#目录结构)
* `docs/@pages` 此文件夹是自动生成的,存放分类页、标签页、归档页对应的`.md`文件,一般不需要改动
* `docs/_posts` 专门用于存放碎片化博客文章,里面的`.md`文件不需要遵循命名约定,不会生成结构化侧边栏和目录页。
* `docs/<结构化目录>` 请查看[《构建结构化站点的核心配置和约定》](/pages/33d574/)。
* `docs/index.md` 首页
* `vdoing` 存放在本地的vdoing主题文件,如果你想深度的修改主题,首先要在`docs/.vuepress/config.js`中配置使用的主题指向这个文件。
<code-group>
<code-block title="config.js" active>
``` js
module.exports = {
// theme: 'vdoing', // npm主题依赖包
theme: require.resolve('../../vdoing'), // 使用本地主题包
}
```
</code-block>
<code-block title="config.ts">
``` typescript
import { resolve } from 'path'
import { defineConfig4CustomTheme } from 'vuepress/config'
import { VdoingThemeConfig } from 'vuepress-theme-vdoing/types'
export default defineConfig4CustomTheme<VdoingThemeConfig>({
// theme: 'vdoing', // 使用npm主题包
theme: resolve(__dirname, '../../vdoing'), // 使用本地主题包
})
```
</code-block>
</code-group>
**注意**:主题的后续维护升级只对npm主题包负责,就是说你使用本地主题就等于放弃了后续的升级服务。因此,建议能在`docs/.vuepress/`内配置和修改的,就尽量不要改动主题内部代码。
---
-->
::: tip 提示
为了方便您更快的学习和使用本主题,我在代码当中添加了比较多的注释说明。
:::
@@ -0,0 +1,373 @@
---
title: Markdown 容器
date: 2020-05-29 11:16:18
permalink: /pages/d0d7eb/
article: false
---
Markdown 容器是对 Markdown 语法的一个扩展,使用简单的语法就可以在页面中呈现丰富的效果。
除了原默认主题自带的容器外,本主题还新增了一些好用的自定义容器。
## 信息框容器
**输入**
```` md
::: tip
这是一条提示
:::
::: warning
这是一条注意
:::
::: danger
这是一条警告
:::
::: note
这是笔记容器,在 <Badge text="v1.5.0 +" /> 版本才支持哦~
:::
````
**输出**
::: tip
这是一条提示
:::
::: warning
这是一条注意
:::
::: danger
这是一条警告
:::
::: note
这是笔记容器,在 <Badge text="v1.5.0 +" /> 以上版本才支持哦~
:::
以上容器均可自定义标题,如:
````
::: tip 我的提示
自定义标题的提示框
:::
````
::: tip 我的提示
自定义标题的提示框
:::
## 布局容器 <Badge text="v1.3.3 +" />
**输入**
```` md
::: center
### 我是居中的内容
(可用于标题、图片等的居中)
:::
::: right
[我是右浮动的内容](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
:::
::: details
这是一个详情块,在 IE / Edge 中不生效
```js
console.log('这是一个详情块')
```
:::
::: theorem 牛顿第一定律
假若施加于某物体的外力为零,则该物体的运动速度不变。
::: right
来自 [维基百科](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
:::
````
**输出**
::: center
### 我是居中的内容
(可用于标题、图片等的居中)
:::
::: right
[我是右浮动的内容](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
:::
::: details
这是一个详情块,在 IE / Edge 中不生效
```js
console.log('这是一个详情块')
```
:::
::: theorem 牛顿第一定律
假若施加于某物体的外力为零,则该物体的运动速度不变。
::: right
来自 [维基百科](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
:::
> 注意:`right`、`details`、`theorem`这三个容器在`v1.3.0 +`版本才支持。`center`容器在`v1.3.3 +`版本才支持。
## 普通卡片列表 <Badge text="v1.1.0 +"/>
普通卡片列表容器,可用于`友情链接`、`项目推荐`、`诗词展示`等。
先来看看效果:
**输出**
::: cardList
```yaml
- name: 麋鹿鲁哟
desc: 大道至简,知易行难
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200122153807.jpg # 可选
link: https://www.cnblogs.com/miluluyo/ # 可选
bgColor: '#CBEAFA' # 可选,默认var(--bodyBg)。颜色值有#号时请添加单引号
textColor: '#6854A1' # 可选,默认var(--textColor)
- name: XAOXUU
desc: '#IOS #Volantis主题作者'
avatar: https://fastly.jsdelivr.net/gh/xaoxuu/assets@master/avatar/avatar.png
link: https://xaoxuu.com
bgColor: '#718971'
textColor: '#fff'
- name: 平凡的你我
desc: 理想成为大牛的小陈同学
avatar: https://reinness.com/avatar.png
link: https://reinness.com
bgColor: '#FCDBA0'
textColor: '#A05F2C'
```
:::
上面效果在Markdown中的代码是这样的:
**输入**
```` md
::: cardList
```yaml
- name: 麋鹿鲁哟
desc: 大道至简,知易行难
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200122153807.jpg # 可选
link: https://www.cnblogs.com/miluluyo/ # 可选
bgColor: '#CBEAFA' # 可选,默认var(--bodyBg)。颜色值有#号时请添加单引号
textColor: '#6854A1' # 可选,默认var(--textColor)
- name: XAOXUU
desc: '#IOS #Volantis主题作者'
avatar: https://fastly.jsdelivr.net/gh/xaoxuu/assets@master/avatar/avatar.png
link: https://xaoxuu.com
bgColor: '#718971'
textColor: '#fff'
- name: 平凡的你我
desc: 理想成为大牛的小陈同学
avatar: https://reinness.com/avatar.png
link: https://reinness.com
bgColor: '#FCDBA0'
textColor: '#A05F2C'
```
:::
````
### 语法
````md
::: cardList <每行显示数量>
``` yaml
- name: 名称
desc: 描述
avatar: https://xxx.jpg # 头像,可选
link: https://xxx/ # 链接,可选
bgColor: '#CBEAFA' # 背景色,可选,默认var(--bodyBg)。颜色值有#号时请添加引号
textColor: '#6854A1' # 文本色,可选,默认var(--textColor)
```
:::
````
* `<每行显示数量>` 数字,表示每行最多显示多少个,选值范围1~4,默认3。在小屏时会根据屏幕宽度减少每行显示数量。
* 代码块需指定语言为`yaml`
* 代码块内是一个`yaml`格式的数组列表
* 数组成员的属性有:
* `name`名称
* `desc`描述
* `avatar`头像,可选
* `link`链接,可选
* `bgColor`背景色,可选,默认`var(--bodyBg)`。颜色值有`#`号时请添加引号
* `textColor`文本色,可选,默认`var(--textColor)`
下面再来看另外一个示例:
**输入**
```` md
::: cardList 2
```yaml
- name: 《静夜思》
desc: 床前明月光,疑是地上霜。举头望明月,低头思故乡。
bgColor: '#F0DFB1'
textColor: '#242A38'
- name: Vdoing
desc: 🚀一款简洁高效的VuePress 知识管理&博客(blog) 主题
link: https://github.com/xugaoyi/vuepress-theme-vdoing
bgColor: '#DFEEE7'
textColor: '#2A3344'
```
:::
````
**输出**
::: cardList 2
```yaml
- name: 《静夜思》
desc: 床前明月光,疑是地上霜。举头望明月,低头思故乡。
bgColor: '#F0DFB1'
textColor: '#242A38'
- name: Vdoing
desc: 🚀一款简洁高效的VuePress 知识管理&博客(blog) 主题
link: https://github.com/xugaoyi/vuepress-theme-vdoing
bgColor: '#DFEEE7'
textColor: '#2A3344'
```
:::
## 图文卡片列表 <Badge text="v1.1.0 +" />
图文卡片列表容器,可用于`项目展示`、`产品展示`等。
先看效果:
**输出**
::: cardImgList
```yaml
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200529162253.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容 # 描述,可选
author: Evan Xu # 作者,可选
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg # 头像,可选
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530100256.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530100257.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
```
:::
**输入**
````md
::: cardImgList
```yaml
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200529162253.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容 # 描述,可选
author: Evan Xu # 作者,可选
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg # 头像,可选
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530100256.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530100257.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
```
:::
````
### 语法
````md
::: cardImgList <每行显示数量>
``` yaml
- img: https://xxx.jpg # 图片地址
link: https://xxx.com # 链接地址
name: 标题
desc: 描述 # 可选
author: 作者名称 # 可选
avatar: https://xxx.jpg # 作者头像,可选
```
:::
````
* `<每行显示数量>` 数字,表示每行最多显示多少个,选值范围1~4,默认3。在小屏时会根据屏幕宽度减少每行显示数量。
* 代码块需指定语言为`yaml`
* 代码块内是一个`yaml`格式的数组列表
* 数组成员的属性有:
* `img`图片地址
* `link`链接地址
* `name`标题
* `desc`描述,可选
* `author`作者名称,可选
* `avatar`作者头像,可选
## 增强配置 <Badge text="v1.9.0 +"/>
为了适应更多需求场景,`v1.9.0+`版本的普通卡片和图文卡片容器添加了一些新的配置:
### 1. 普通卡片和图文卡片容器
#### target
- 链接的打开方式,默认`_blank`
- `_self` 当前页面
- `_blank` 新窗口打开
### 2. 图文卡片容器
#### imgHeight
- 设置图片高度,默认 `auto`
- 带单位
#### objectFit
- 设置图片的填充方式(object-fit),默认 `cover`
- `fill` 拉伸 (会改变宽高比)
- `contain` 缩放 (保持宽高比,会留空)
- `cover` 填充 (会裁剪)
- `none` 保持原有尺寸 (会留空或裁剪)
- `scale-down` 保证显示完整图片 (保持宽高比,会留空)
#### lineClamp
- 描述文本超出多少行显示省略号,默认`1`
### 3. 配置示例:
````yaml
::: cardImgList
``` yaml
config:
target: _blank
imgHeight: auto
objectFit: cover
lineClamp: 1
data:
- img: https://xxx.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容
author: Evan Xu
avatar: https://xxx.jpg
```
:::
````
@@ -0,0 +1,70 @@
---
title: Markdown 中使用组件
date: 2020-11-10 18:56:22
permalink: /pages/197691/
article: false
---
主题的内置组件可以直接在`Markdown`文件中以类似html标签的方式使用。
## 标记
- **Props:**
- `text`- string
- `type` - string, 可选值: `tip | warning | error`,默认: `tip`
- `vertical` - string, 可选值: `top | middle`,默认: `top`
- **Usage:**
你可以在标题或其他内容中使用标记:
```md
#### 《沁园春·雪》 <Badge text="摘"/>
北国风光<Badge text="注释" type="warning"/>,千里冰封,万里雪飘。
> <Badge text="译文" type="error" vertical="middle"/>: 北方的风光。
```
**效果:**
#### 《沁园春·雪》 <Badge text="摘"/>
北国风光<Badge text="注释" type="warning"/>,千里冰封,万里雪飘。
> <Badge text="译文" type="error" vertical="middle"/>: 北方的风光。
## 代码块选项卡 <Badge text="v1.8.0 +"/>
`<code-group>`中嵌套`<code-block>`来配合使用。在`<code-block>`标签添加`title`来指定tab标题,`active`指定当前tab
````md
<code-group>
<code-block title="YARN" active>
```bash
yarn add vuepress-theme-vdoing -D
```
</code-block>
<code-block title="NPM">
```bash
npm install vuepress-theme-vdoing -D
```
</code-block>
</code-group>
````
**效果:**
<code-group>
<code-block title="YARN" active>
```bash
yarn add vuepress-theme-vdoing -D
```
</code-block>
<code-block title="NPM">
```bash
npm install vuepress-theme-vdoing -D
```
</code-block>
</code-group>
::: warning
- 请在`<code-group>`标签与markdown内容之间使用空行隔开,否则可能会解析不出来。
- 该组件只适用于放置代码块,放其他内容在体验上并不友好。如您确实需要放置其他内容的选项卡,推荐使用[vuepress-plugin-tabs](https://superbiger.github.io/vuepress-plugin-tabs)插件。
:::
@@ -0,0 +1,26 @@
---
title: 使目录栏支持h2~h6标题
date: 2022-03-18 15:02:52
permalink: /pages/8dfab5/
article: false
---
`.vuepress/config.js`添加如下配置即可使 VuePress 提取相应标题级别的数据,并应用到主题的右侧目录栏中<Badge text="v1.10.0 +"/>。
## markdown.extractHeaders
- 类型: Array
- 默认: ['h2', 'h3']
Markdown 文件的 headers (标题 & 小标题) 会在准备阶段被提取出来,并存储在 this.$page.headers 中。默认情况下,VuePress 会提取 h2 和 h3 标题。你可以通过这个选项来修改提取出的标题级别。
```js
module.exports = {
markdown: {
extractHeaders: [ 'h2', 'h3', 'h4', 'h5', 'h6' ]
}
}
```
注:此配置来自 [VuePress官方文档](https://vuepress.vuejs.org/zh/config/#markdown-extractheaders)
@@ -0,0 +1,230 @@
---
title: 如何让你的笔记更有表现力
date: 2020-09-26 21:13:59
permalink: /pages/dd027d/
article: false
---
你的知识笔记枯燥无味没有重点?基于本主题,配合各种骚操作,让你的知识笔记表现力爆棚~~
::: note
Markdown的基本语法就不再重复啦 (对Markdown不了解的可以看 [这里](https://xugaoyi.com/pages/ad247c4332211551/)),
下面将介绍一些可以在本主题Markdown中使用的骚操作~
:::
## 1. 文本高亮
使用`<mark>`标签让文本高亮
```text
Vdoing是一款简洁高效的 <mark>知识管理&博客</mark> 主题
```
Vdoing是一款简洁高效的 <mark> 知识管理&博客 </mark> 主题
## 2. 标记
### 内置标记
主题内置的[Badge组件](https://vuepress.vuejs.org/zh/guide/using-vue.html#badge),直接在 Markdown 文件中使用
```html
<Badge text="beta" type="warning"/>
<Badge text="Vdoing主题"/>
```
<Badge text="beta" type="warning"/>
<Badge text="Vdoing主题"/>
### 外部标记
使用 [shields](https://shields.io/) 生成标记,在Markdown中使用
```markdown
![npm](https://img.shields.io/npm/v/vuepress-theme-vdoing)
![star](https://img.shields.io/github/stars/xugaoyi/vuepress-theme-vdoing)
```
![npm](https://img.shields.io/npm/v/vuepress-theme-vdoing)
![star](https://img.shields.io/github/stars/xugaoyi/vuepress-theme-vdoing)
> 这类标记图标可以生成动态统计数据。
## 3. 折叠列表
主题内置的容器,直接在 Markdown 文件中使用
````html
::: details
这是一个详情块
```js
console.log('这是一个详情块')
```
:::
````
::: details
这是一个详情块
```js
console.log('这是一个详情块')
```
:::
> 更多:[Markdown 容器](/pages/d0d7eb/)
## 4. 思维导图 & 流程图
### 方法一:
1. 使用[Markmap](https://markmap.js.org/)生成思维导图html文件
2. 将html文件放在`docs/.vuepress/public/markmap/`
3. 通过`<iframe>`插入到Markdown
``` html
<iframe :src="$withBase('/markmap/01.html')" width="100%" height="400" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
```
<iframe :src="$withBase('/markmap/01.html')" width="100%" height="400" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
### 方法二:
通过`<iframe>`标签引入[processon](https://www.processon.com/)或其他在线作图工具生成的链接。
```html
<iframe src="https://www.processon.com/view/link/5e718942e4b015182028682c" width="100%" height="500" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
```
<iframe src="https://www.processon.com/view/link/5e718942e4b015182028682c" width="100%" height="500" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
### 方法三:
使用流程图插件:
* [vuepress-plugin-flowchart](https://www.npmjs.com/package/vuepress-plugin-flowchart)
* [vuepress-plugin-mermaidjs](https://github.com/eFrane/vuepress-plugin-mermaidjs)
## 5.Demo演示框
### 方法一:
1. 安装 [vuepress-plugin-demo-block](https://www.npmjs.com/package/vuepress-plugin-demo-block)或其他同类插件,使用方法看插件文档
2. 在`.vuepress/config.js`配置插件
3. Markdown中使用
> 同类插件:[vuepress-plugin-demo-container](https://github.com/calebman/vuepress-plugin-demo-container)
::: demo [vanilla]
```html
<html>
<div class="animationBox">
<div class="rotate">旋转动画1</div>
<div class="play">
<div class="img">旋转动画2</div>
<span><p class="p2"></p></span>
<span><p></p></span>
<span><p></p></span>
<span><p class="p2"></p></span>
</div>
<div class="elasticity">弹性动画</div>
<div class="elasticity2">曲线弹性</div>
</div>
</html>
<style>
.animationBox{overflow: hidden;}
.animationBox>div{
width: 100px;height: 100px;background: #eee;border-radius: 50%;text-align: center;line-height: 100px;margin: 30px;float:left;
}
.rotate{
animation: rotate 5s linear infinite
}
.rotate:hover{ animation-play-state: paused}
@keyframes rotate {
0%{transform: rotate(0);}
100%{transform: rotate(360deg);}
}
.animationBox>.play {
position: relative;
margin: 50px 30px;
background:none;
}
.play .img{
position: absolute;
top: 0;
left:0;
z-index: 1;
width: 100px;height: 100px; background: #eee;
border-radius: 50%;
animation: rotate 5s linear infinite
}
.play span {
position: absolute;
top: 1px;
left:1px;
z-index: 0;
display: block;
width: 96px;
height: 96px;
border: 1px solid #999;
border-radius: 50%;
}
.play span p{display: block;width: 4px;height: 4px;background: #000;margin: -2px 0 0 50%;border-radius: 50%;opacity: 0.5;}
.play span .p2{margin: 50% 0 0 -2px;}
.play span{
animation: wave 5s linear infinite
}
.play>span:nth-child(3){
/* 延迟时间 */
animation-delay:1s;
}
.play>span:nth-child(4){
animation-delay:2.2s;
}
.play>span:nth-child(5){
animation-delay:3.8s;
}
@keyframes wave {
0%
{
transform:scale(1) rotate(360deg);
opacity: 0.8;
}
100%
{
transform:scale(1.8) rotate(0deg);
opacity: 0;
}
}
.elasticity{
animation: elasticity 1s linear 2s infinite
}
@keyframes elasticity{
0%{
transform: scale(0);
}
60%{
transform: scale(1.1);
}
90%{
transform: scale(1);
}
}
.elasticity2{
animation: elasticity2 1s cubic-bezier(.39,.62,.74,1.39) 2s infinite
}
@keyframes elasticity2{
0%{
transform: scale(0);
}
90%{
transform: scale(1);
}
}
</style>
```
:::
### 方法二:
嵌入[codepen](https://codepen.io/)
```html
<iframe height="400" style="width: 100%;" scrolling="no" title="【CSS:行为】使用:hover和attr()定制悬浮提示" src="https://codepen.io/xugaoyi/embed/vYNKNaq?height=400&theme-id=light&default-tab=css,result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
See the Pen <a href='https://codepen.io/xugaoyi/pen/vYNKNaq'>【CSS:行为】使用:hover和attr()定制悬浮提示</a> by xugaoyi
(<a href='https://codepen.io/xugaoyi'>@xugaoyi</a>) on <a href='https://codepen.io'>CodePen</a>.
</iframe>
```
<iframe height="400" style="width: 100%;" scrolling="no" title="【CSS:行为】使用:hover和attr()定制悬浮提示" src="https://codepen.io/xugaoyi/embed/vYNKNaq?height=400&theme-id=light&default-tab=css,result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
See the Pen <a href='https://codepen.io/xugaoyi/pen/vYNKNaq'>【CSS:行为】使用:hover和attr()定制悬浮提示</a> by xugaoyi
(<a href='https://codepen.io/xugaoyi'>@xugaoyi</a>) on <a href='https://codepen.io'>CodePen</a>.
</iframe>
::: note
`<iframe>`标签还可以嵌入其他任何外部网页,如视频、地图等
:::
@@ -0,0 +1,67 @@
---
title: 批量操作front matter工具
date: 2020-05-13 11:52:45
permalink: /pages/2b8e22
article: false
---
当你想为某个文件夹下的所有`.md`文件添加、修改、删除某些front matter字段时,这个工具可以快速的为你批量操作。
首先,你需要在`package.json``scripts`中写入脚本:
```json
// package.json
{
"scripts": {
"editFm": "node utils/editFrontmatter.js",
}
}
```
`utils/config.yml`配置要批量操作的文件夹和要编辑的字段,示例:
```yaml
# utils/config.yml
#批量添加和修改、删除front matter配置文件
# 需要批量处理的路径,docs文件夹内的文件夹 (数组。映射路径:docs/arr[1]/arr[2] ... )
path:
- docs # 第一个成员必须是docs
- 《JS教程》专辑
- 第一章节
# 要删除的字段 (数组)
delete:
- article
# 要添加、修改front matter的数据 front matter中没有的数据则添加,已有的数据则覆盖)
data:
author: 齐天大圣
sidebar: false
```
比如你要操作的文件夹路径是`docs/《JS教程》专辑/第一章节`,你需要这样配置路径:
```yaml
path:
- docs # 第一个成员必须是docs
- 《JS教程》专辑
- 第一章节
```
`path`数组的第一个成员必须是`docs`,如果你想操作`docs`底下除了首页之外所有的`.md`文件,只需保留第一个成员`docs`即可。
你想删除`article`字段:
```yaml
delete:
- article
```
你想为这个文件夹下的所有`.md`文件添加作者`author`和隐藏侧边栏`sidebar`
```yaml
data:
author: 齐天大圣
sidebar: false
```
最后,执行`npm run editFm`命令,为了防止误操作,会有一个询问过程:
```sh
npm run editFm
? 批量操作frontmatter有修改数据的风险,确定要继续吗? (Y/n)
...
```
@@ -0,0 +1,84 @@
---
title: 部署
date: 2020-05-13 12:10:53
permalink: /pages/0fc1d2
article: false
---
::: warning
目前作者使用的部署方式已改为 [vercel](https://vercel.com/),部署方法参考 [这里](https://zhuanlan.zhihu.com/p/347990778)。(2022.01.01</br>
更多 [部署方式](https://vuepress.vuejs.org/zh/guide/deploy.html#%E9%83%A8%E7%BD%B2)
:::
本项目内置了两种`自动部署`脚本,用于一键部署到 GitHub Pages 或 国内访问速度更快的Coding Pages
## 1.使用deploy.sh脚本部署
第一步,修改`deploy.sh`脚本内的仓库地址为你的仓库,如有自定义域名则一并修改,没有则注释
```bash
# 如果是发布到自定义域名
echo 'xugaoyi.com' > CNAME
# 如果发布到 https://<USERNAME>.github.io
# git push -f [email protected]:<USERNAME>/<USERNAME>.github.io.git master
# 如果发布到 https://<USERNAME>.github.io/<REPO>
# git push -f [email protected]:<USERNAME>/<REPO>.git master:gh-pages
```
第二步,一键部署命令
```bash
npm run deploy
```
> windows系统下使用bash命令窗
第三步,设置仓库的GitHub Pages。
> `deploy.sh`文件看 [这里](https://github.com/xugaoyi/vuepress-theme-vdoing/blob/master/deploy.sh)
> 看不懂shell代码? 参考:[shell教程](https://ipcmen.com/)
## 2. 使用GitHub Action自动持续集成
第一步,按照[官方文档](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line),生成一个github token (令牌)。
第二步,将这个密钥储存到当前仓库的`Settings/Secrets`里面。
> `Settings/Secrets`是储存秘密的环境变量的地方。环境变量的名字可以随便起,这里用的是`ACCESS_TOKEN`。如果你不用这个名字,`.github/workflows/ci.yml`脚本里的变量名也要跟着改。
第三步,push提交代码到GitHub仓库master分支。
第四步,设置仓库的GitHub Pages
> 参考 [GitHub Action实现自动部署静态博客](http://xugaoyi.com/pages/6b9d359ec5aa5019/)
> `ci.yml`文件看 [这里](https://github.com/xugaoyi/blog/blob/master/.github/workflows/ci.yml)
> 看不懂yaml代码? 参考:[yaml教程](https://xugaoyi.com/pages/4e8444e2d534d14f/)
## 部署升级:同时部署到github和coding
为了让博客站能够让百度收录,因此我把博客部署到了coding。相关[文章](https://xugaoyi.com/pages/41f87d890d0a02af/)。
在原有部署方式上做了升级,主要修改代码文件有下面这两个:
使用前先将[github token (令牌) ](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line)和[coding token (令牌)](https://dev.tencent.com/help/doc/account/access-token) 同时设置到github仓库的`Settings/Secrets`位置。
![token设置](https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/token.jpg)
### 部署方式有两种:
#### 1) 、使用如下命令一键部署到github和coding
```sh
npm run deploy
```
#### 2)、使用GitHub Action自动部署到github和coding
只需要push提交代码到github仓库master分支即可。
> 参考 [《GitHub Action实现自动部署静态博客》](http://xugaoyi.com/pages/6b9d359ec5aa5019/)
@@ -0,0 +1,30 @@
---
title: 关于写文章和一级标题
date: 2020-05-13 12:07:32
permalink: /pages/9ae0bd
article: false
---
写文章时,大概的流程是这样的:
1.首先是创建`.md`文件,如果在结构化目录下则**需要**添加相应的序号,在碎片化博文目录`_posts`下**不需要**添加序号,文件名填写文章的标题。
2.如果你想手动指定文章的分类、标签,则需要在头部front matter填写相应的字段。一般情况下不需要再填写其他字段。不想手动指定分类和标签也可以省略这一步。
``` yaml
---
categories:
- 分类1
tags:
- 标签1
- 标签2
---
```
3.**关于一级标题**,即markdown中的一级标题(`# 标题`),你可以写也可以不写,在页面中实际显示的文章标题是取自front matter中的`title`,然后把`.md`中的一级标题隐藏了。
> 这样做即可以保留本地文件的一级标题,也可以兼容线上页面的标题。
> 同时,对于在新建`.md`文件时已经输入了一次标题在文件名,不想在文档中重复输入一次标题的,也是可以实现的。
4.**关于文章摘要**,你想在首先文章列表中显示摘要时可以在合适的位置添加一个`<!-- more -->`注释,参考:[添加摘要](/pages/1cc523/)
5.最后,就可以正式开始写作啦。
@@ -0,0 +1,35 @@
---
title: 关于博客搭建与管理
date: 2020-05-13 12:12:33
permalink: /pages/26997d
article: false
---
这里是我在搭建和管理博客过程中写的一些文章和小技巧。
1. 评论模块的搭建
[使用Gitalk实现静态博客无后台评论系统](https://xugaoyi.com/pages/1da0bf9a988eafe5/)
2. 自定义域名及解析,[详情](https://github.com/xugaoyi/vuepress-theme-vdoing/issues/326)
3. SEO相关
```js
// config.js
module.exports = {
description: '填写网站描述', // 以 <meta> 标签渲染到页面html中
head: [ // 注入到页面<head> 中的标签,[tagName, { attrName: attrValue }]
['meta', { name: 'keywords', content: '填写关键字'}]
]
}
```
4. 图床
[GitHub + jsDelivr + TinyPNG+ PicGo 打造稳定快速、高效免费图床](https://xugaoyi.com/pages/a5f73af5185fdf0a/)
5. 结合GitHub Actions开发的每天定时百度推送,加快收录
[GitHub Actions 定时运行代码:每天定时百度链接推送](https://xugaoyi.com/pages/f44d2f9ad04ab8d3/)
@@ -0,0 +1,31 @@
---
title: 在线编辑和新增文章的方法
date: 2020-05-19 11:17:58
permalink: /pages/c5a54d
article: false
---
::: warning 说明
以下方法实现的前提是把博客源码上传到github仓库,并配置好 [GitHub Actions](https://github.com/features/actions) 自动部署。
:::
### 在线编辑原有的文章
首先,在`config.js`启用页面的编辑按钮,并配置好github仓库和`.md`文件所在根目录等,详见 [官方文档](https://vuepress.vuejs.org/zh/theme/default-theme-config.html#git-%E4%BB%93%E5%BA%93%E5%92%8C%E7%BC%96%E8%BE%91%E9%93%BE%E6%8E%A5)。
示例:
```js
// config.js
module.exports = {
themeConfig: {
repo: 'xugaoyi/vuepress-theme-vdoing', // Github仓库地址
docsDir: 'docs', // .md文件放在了docs目录下
editLinks: true, // 启用编辑链接
editLinkText: '编辑',
}
}
```
配置好之后,每个文章页面底下都会有一个编辑按钮,点击即可跳到github在线编辑,编辑完成后提交就会自动触发GitHub Actions自动部署。
### 在线新增文章
在github博客源码仓库相应的文章目录下,新建`.md`文件,编辑好文章并提交后会触发GitHub Actions自动部署。
> 提示:当使用过在线编辑或新增后,下次在本地编辑之前先拉取代码。
@@ -0,0 +1,518 @@
---
title: 主题配置
date: 2020-05-12 14:57:21
permalink: /pages/a20ce8
article: false
---
主题的配置在`.vuepress/config.ts`文件的`themeConfig`字段中,是在原有配置的基础上做的新增和修改,配置示例请查看:[config.ts](https://github.com/xugaoyi/vuepress-theme-vdoing/blob/master/docs/.vuepress/config.ts)。
更多官方配置,请查看[vuepress文档](https://vuepress.vuejs.org/zh/)。
## 是否打开分类、标签、归档
### category
* 类型:`boolean`
* 默认:true
是否打开分类功能。 如打开,会做的事情有:
1. 自动生成的front matter包含分类字段
2. 页面中显示与分类相关的信息和模块
3. 自动生成分类页面(在@pages文件夹
如关闭,则反之。
### tag
* 类型:`boolean`
* 默认:true
是否打开标签功能。 如打开,会做的事情有:
1. 自动生成的front matter包含标签字段
2. 页面中显示与标签相关的信息和模块
3. 自动生成标签页面(在@pages文件夹
如关闭,则反之。
### archive
* 类型:`boolean`
* 默认:true
是否打开归档功能。 如打开,会做的事情有:
1. 自动生成归档页面(在@pages文件夹
如关闭,则反之。
::: tip
如果你仅仅是想使用这个主题来搭建知识库,并不想使用分类、标签、归档功能,就可以关闭它们。
:::
### 使用分类页、标签页、归档页链接
当你打开了分类、标签、归档功能,就可以在导航或其他地方添加分类页、标签页、归档页的链接:
* 分类页: `/categories/`
* 标签页: `/tags/`
* 归档页: `/archives/`
## 碎片化文章默认分类值
### categoryText
* 类型:`string`
* 默认:'随笔'
碎片化文章(_posts文件夹的文章)默认生成的分类值
## 页面风格
### pageStyle <Badge text="v1.12.0 +"/>
- 类型:`string`
- 可选值:'card' | 'line' line在未设置bodyBgImg时才生效)
- 默认:'card'
页面风格,`card`时背景显示灰色衬托出卡片样式,`line`时背景显示纯色,并且部分模块带线条边框(未设置bodyBgImg时才生效)
### defaultMode <Badge text="v1.12.3 +"/>
- 类型:`string`
- 可选值:'auto' | 'light' | 'dark' | 'read'
- 默认:'auto'
默认外观模式,用户未在页面手动修改过模式时才生效,否则以用户设置的模式为准
## body背景大图
### bodyBgImg
* 类型:`string` | `array`
* 默认:undefined
body背景大图,单张图片使用String,多张图片使用Array 多张图片时每隔 \<bodyBgImgInterval\> 秒换一张
### bodyBgImgOpacity <Badge text="v1.4.0 +"/>
* 类型:`number`
* 默认:0.5
* 选值范围:0 ~ 1.0
body背景图透明度
### bodyBgImgInterval <Badge text="v1.12.0 +"/>
* 类型:`number`
* 默认:15
* 单位:s
body有多张背景大图时的切换间隔
## 文章标题前的图标
### titleBadge
* 类型:`boolean`
* 默认:true
是否打开文章标题图标
### titleBadgeIcons
* 类型:`array`
* 默认:内置图标
文章标题图标的地址
## 文章内容块的背景底纹
### contentBgStyle <Badge text="v1.4.0 +"/>
* 类型:`number`
* 默认:undefined
* 选值:1 => 方格 | 2 => 横线 | 3 => 竖线 | 4 => 左斜线 | 5 => 右斜线 | 6 => 点状
文章内容块的背景底纹
## 侧边栏
### sidebar
* 类型:`srting` | `object` | `array`
* 在默认主题原有的配置上新增两项参数:
* `'structuring'` 自动生成结构化侧边栏
* `{ mode: 'structuring', collapsable: Boolean}` 自动生成结构化侧边栏,并设置侧边栏是否可折叠,默认true
::: tip
如需构建结构化站点请把此配置设置为`structuring``{ mode: 'structuring', collapsable: false}`
:::
### sidebarOpen
* 类型:`boolean`
* 默认:true
初始状态下是否打开侧边栏
::: tip
在侧边栏关闭状态下,页面向下滚动时会隐藏顶部导航栏,让用户更专注于阅读。
:::
### 对指定页面禁用侧边栏
你可以通过 front matter 来禁用指定页面的侧边栏:
``` yaml
---
sidebar: false
---
```
### 碎片化文章的侧边栏
在_posts文件夹的文章会自动在 front matter 添加 `sidebar: auto`
``` yaml
---
sidebar: auto
---
```
## 最近更新栏
### updateBar
* 类型:`object`
* 默认:`{showToArticle: true, moreArticle: '/archives/'}`
* showToArticle 显示到文章页底部,默认true
* moreArticle “更多文章”跳转的页面,默认'/archives/'
最近更新栏,显示于文章页底部和简约版首页文章列表
### 非文章页的设置
对于非文章页,如目录页、关于、友情链接等自定义页面,最好在front matter设置`article: false`,设置之后这个页面将被认定为非文章页,不显示面包屑和作者、时间,不显示最近更新栏,不会参与到最近更新文章的数据计算中。
```yaml
---
article: false
---
```
## 右侧文章大纲栏
### rightMenuBar <Badge text="v1.6.3 +"/>
* 类型:`boolean`
* 默认:true
是否显示右侧文章大纲栏。设置为`false`或屏宽小于`1300px`时,文章大纲将与左侧侧边栏混合在一起。
(注:在屏宽小于`1300px`下无论如何都不显示右侧文章大纲栏。)
## 快捷翻页按钮
### pageButton <Badge text="v1.4.3 +"/>
* 类型:`boolean`
* 默认:true
是否显示快捷翻页按钮 (此按钮是文章页左右两边的箭头按钮,小屏中不会显示。)
## 文章作者信息
### author
* 类型:`string` | `{name: String, link: String}`
* 默认:undefined
* 属性:
* name 作者名称
* link 作者链接
文章默认的作者信息
### 指定的文章作者信息
你也可以在指定的文章front matter设置作者信息,优先级比默认作者信息高,示例:
```yaml
---
author:
name: 作者名
link: https://xxx.com
---
---
author: 作者名
---
```
## 博主信息
### blogger
* 参数和类型:`{avatar: String, name: String, slogan: String}`
* 默认:undefined
* avatar 头像,必需
* name 博主名称,必需
* slogan 标语,可选
博主信息显示于首页博主信息栏
## 社交图标
### social
* 参数和类型:`{iconfontCssFile: String, icons: [{iconClass: String, title: String, link: String}]}`
* 默认:undefined
* iconfontCssFile 可选,阿里图标库(或其他)的在线css字体图标文件地址,对于主题没有的图标可自由添加
* icons 图标列表,数量自由
* iconClass 图标的Class名称
* title 图标的title
* link 图标的跳转链接
社交图标显示于博主信息栏和页脚栏
### 主题内置的社交图标 <Badge text="v1.2.2+, 部分v1.7.2+" />
<table class="icons-table">
<tbody>
<tr>
<td align="center" valign="middle">
<span class="iconfont icon-weixin"></span>
<p class="name">微信</p>
<p>icon-weixin</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-QQ"></span>
<p class="name">QQ</p>
<p>icon-QQ</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-youjian"></span>
<p class="name">邮件</p>
<p>icon-youjian</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-npm"></span>
<p class="name">npm</p>
<p>icon-npm</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-github"></span>
<p class="name">github</p>
<p>icon-github</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-gitee"></span>
<p class="name">gitee</p>
<p>icon-gitee</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-weibo"></span>
<p class="name">微博</p>
<p>icon-weibo</p>
</td>
</tr><tr></tr>
<tr>
<td align="center" valign="middle">
<span class="iconfont icon-zhihu"></span>
<p class="name">知乎</p>
<p>icon-zhihu</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-yuque"></span>
<p class="name">语雀</p>
<p>icon-yuque</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-douban"></span>
<p class="name">豆瓣</p>
<p>icon-douban</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-juejin"></span>
<p class="name">掘金</p>
<p>icon-juejin</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-jianshu"></span>
<p class="name">简书</p>
<p>icon-jianshu</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-sf"></span>
<p class="name">思否</p>
<p>icon-sf</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-bokeyuan"></span>
<p class="name">博客园</p>
<p>icon-bokeyuan</p>
</td>
</tr><tr></tr>
<tr>
<td align="center" valign="middle">
<span class="iconfont icon-csdn"></span>
<p class="name">CSDN</p>
<p>icon-csdn</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-v2ex"></span>
<p class="name">v2ex</p>
<p>icon-v2ex</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-douyin"></span>
<p class="name">抖音</p>
<p>icon-douyin</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-bilibili"></span>
<p class="name">哔哩哔哩</p>
<p>icon-bilibili</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-youtube"></span>
<p class="name">youtube</p>
<p>icon-youtube</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-facebook"></span>
<p class="name">facebook</p>
<p>icon-facebook</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-twitter"></span>
<p class="name">twitter</p>
<p>icon-twitter</p>
</td>
</tr><tr></tr>
<tr>
<td align="center" valign="middle">
<span class="iconfont icon-telegram"></span>
<p class="name">telegram</p>
<p>icon-telegram</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-rss"></span>
<p class="name">RSS</p>
<p>icon-rss</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-erji"></span>
<p class="name">耳机</p>
<p>icon-erji</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-mao"></span>
<p class="name">猫咪</p>
<p>icon-mao</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-shuben"></span>
<p class="name">书本</p>
<p>icon-shuben</p>
</td>
<td align="center" valign="middle">
<span class="iconfont icon-xiangce"></span>
<p class="name">相册</p>
<p>icon-xiangce</p>
</td>
</tr>
</tbody>
</table>
<style>
.icons-table{
opacity: 0.8;
}
.icons-table td{
padding: 1em;
}
.icons-table span::before{
font-size: 26px;
}
.icons-table p.name{
margin-top: 18px;
font-size: 14px;
}
.icons-table p{
margin: 10px 0 0 0;
font-size: 15px;
line-height: 15px;
}
</style>
## 扩展自动生成front matter
### extendFrontmatter <Badge text="v1.11.0 +"/>
- 类型:`Object`
- 默认:undefined
当`.md`文件的front matter不存在extendFrontmatter内相应的字段时,将在运行开发服务`dev`或打包`build`时自动添加,但不会覆盖已有的数据。
**例子:**
```js
extendFrontmatter: {
author: {
name: 'xugaoyi',
link: 'https://github.com/xugaoyi'
},
titleTag: '',
}
```
生成到front matter
```yaml
---
author:
name: xugaoyi
link: https://github.com/xugaoyi
titleTag:
---
```
## 页脚版权栏
### footer
* 参数和类型:`{createYear: Number | String, copyrightInfo: String}`
* 默认:undefined
* createYear 博客创建的年份
* copyrightInfo 可以配置包括版权信息、备案信息在内的所有信息,支持a标签
页脚版权栏信息,原默认主题在首页的front matter中的`footer`配置项已弃用。
## 自定义html模块
> 可用于插入广告模块
### htmlModules <Badge text="v1.7.0 +"/>
* 类型:`object`
* 默认:undefined
* 属性:
* `homeSidebarB` 首页侧边栏底部
* `sidebarT` 所有左侧边栏顶部
* `sidebarB` 所有左侧边栏底部
* `pageT` 页面顶部
* `pageB` 页面底部
* `pageTshowMode` 页面顶部的显示方式
* `未配置` 默认所有页面显示
* `'article'` 仅文章页①显示
* `'custom'` 仅自定义页①显示
* `pageBshowMode` 页面底部的显示方式
* `未配置` 默认全局显示
* `'article'` 仅文章页①显示
* `'custom'` 仅自定义页①显示
* `windowLB` 全局窗口左下角②
* `windowRB` 全局窗口右下角②
<br/>
> ①注:在.md文件front matter配置`article: false`的页面是自定义页,未配置的默认是文章页(首页除外)。
>
> ②注:windowLB 和 windowRB1.展示区块最大宽高200px\*400px。2.请给自定义元素定一个不超过200px\*400px的宽高。3.在屏幕宽度小于960px时无论如何都不会显示。
* 格式:
```js
htmlModules: {
homeSidebarB: htmlString,
sidebarT: htmlString,
sidebarB: htmlString,
pageT: htmlString,
pageB: htmlString,
pageTshowMode: 'article' | 'custom',
pageBshowMode: 'article' | 'custom',
windowLB: htmlString,
windowRB: htmlString,
}
```
@@ -0,0 +1,86 @@
---
title: 首页配置
date: 2020-05-12 15:36:50
permalink: /pages/f14bdb
article: false
---
`docs`目录下的`index.md``README.md`的 front matter 指定 `home: true`,就会为你的站点生成一个首页,示例:
```yaml
---
home: true
# heroImage: /img/web.png
heroText: Evan's blog
tagline: Web前端技术博客,积跬步以至千里,致敬每个爱学习的你。
# actionText: 立刻进入 →
# actionLink: /web/
# bannerBg: auto # auto => 网格纹背景(有bodyBgImg时无背景),默认 | none => 无 | '大图地址' | background: 自定义背景样式 提示:如发现文本颜色不适应你的背景时可以到palette.styl修改$bannerTextColor变量
features: # 可选的
- title: 前端
details: JavaScript、ES6、Vue框架等前端技术
link: /web/ # 可选
imgUrl: /img/web.png # 可选
- title: 页面
details: html(5)/css(3),前端页面相关技术
link: /ui/
imgUrl: /img/ui.png
- title: 技术
details: 技术文档、教程、技巧、总结等文章
link: /technology/
imgUrl: /img/other.png
# 文章列表显示方式: detailed 默认,显示详细版文章列表(包括作者、分类、标签、摘要、分页等)| simple => 显示简约版文章列表(仅标题和日期)| none 不显示文章列表
# postList: detailed
# simplePostListLength: 10 # 简约版文章列表显示的文章数量,默认10。(仅在postList设置为simple时生效)
# hideRightBar: true # 是否隐藏右侧边栏 (v1.11.2+)
---
```
一些字段还是沿用[默认主题](https://vuepress.vuejs.org/zh/theme/default-theme-config.html#%E9%A6%96%E9%A1%B5)的,这里只对修改的地方做一个补充。
### bannerBg
* 类型:`string`
* 可选参数:
* `auto` 自动背景,一般会显示网格纹背景,如果在`config.js`设置了`bodyBgImg`时则无背景
* `none` 无背景
* `<大图地址>`,如`/img/bg.jpeg`
* `background: <自定义背景样式>`,如`background: blue`
* 默认: `auto`
### features
* 类型:`{title: string, details: string, link?: string, imgUrl?: string}[]`
features是在banner栏显示的特性描述,主题添加了图片的展示和点击跳转的链接
* **features[index].link** 当前feature跳转的链接,可选
* **features[index].imgUrl** 当前feature的图片地址,可选
### postList
* 类型:`'detailed' | 'simple' | 'none'`
* 可选参数:
* `detailed` 显示详细版文章列表(包括标题、日期、作者、分类、标签、摘要、分页等)
* `simple` 显示简约版文章列表(仅标题和日期)
* `none` 不显示文章列表
* 默认: `detailed`
首页内容中的文章列表显示方式
### simplePostListLength <Badge text="v1.5.1 +"/>
* 类型:`number`
* 默认: `10`
简约版文章列表显示的文章数量,默认`10`。(仅在`postList`设置为`simple`时生效)
### hideRightBar <Badge text="v1.11.2 +"/>
* 类型:`boolean`
* 默认: `false`
是否隐藏右侧边栏
::: warning
原默认主题首页的footer字段已改到`config.js`文件里设置
:::
@@ -0,0 +1,68 @@
---
title: front matter配置
date: 2020-05-12 15:37:00
permalink: /pages/3216b0
article: false
---
一个比较完整的front matter示例:
```yaml
---
title: 标题
date: 2020-02-21 14:40:19
permalink: /pages/a1bc87
categories:
- 分类1
- 分类2
tags:
- 标签1
titleTag: 原创 # v1.9.0+
sidebar: false
article: false
comment: false
editLink: false
author:
name: 作者
link: https://xxx.com
sticky: 1
---
```
## 配置项
自动生成的front matter字段包括title、date、permalink、categories、tags,这里就不再重复赘述,参考:[自动生成front matter](/pages/088c16/)
### titleTag <Badge text="v1.9.0 +"/>
* 用于给标题添加 `原创``转载``优质``推荐` 等自定义标记。
添加了标题标记的文章,在文章页和文章列表、最近更新栏、归档页的文章标题都会显示此标记。
### sidebar
* `false` 不显示侧边栏
* `auto` 显示自动侧边栏(只包含本文标题和子标题)
### article
* `false` 判定当前页面为非文章页
对于非文章页,如目录页、关于、友情链接等自定义页面,需要设置此项。设置之后这个页面将被认定为非文章页,不显示面包屑和作者、时间,不显示最近更新栏,不会参与到最近更新文章的数据计算中。
### comment
* `false` 不显示评论区,这是[评论插件](https://github.com/dongyuanxin/vuepress-plugin-comment)的一个配置
### editLink
* `false` 不显示编辑链接
### author
* author.name 作者名称
* author.link 作者链接
指定当前页面的作者信息,如没有作者链接时可以这样:`author: 作者名称`
### sticky (置顶)
* 类型: `number`
* 排序:允许有多个置顶文章,按照 `1, 2, 3, ...` 来降低置顶文章的排列优先级
文章置顶,设置了此项将在首页详细版文章列表中处于置顶位置。
@@ -0,0 +1,94 @@
---
title: 目录页配置
date: 2020-05-13 10:58:07
permalink: /pages/54651a
article: false
---
## 目录页说明
::: warning
目录页数据需要依赖于结构化的侧边栏数据,就是说你需要在`config.js`配置 `sidebar: 'structuring'``sidebar: { mode: 'structuring', collapsable: false}` 才能实现目录页数据的获取。
:::
> - 目录页文件(`.md`文件)可以放置在`二级目录`、`三级目录`和`四级目录`。([级别说明](/pages/33d574/#级别说明)
> - 如果你不想在侧边栏显示目录页,你可以在`一级目录`中单独创建一个文件夹放置你的目录页(`.md`文件),并在front matter中设置`sidebar: false`。
> - 如果你想让目录页和其他页面一起出现在侧边栏,你可以和其他页面共同放置在相应的文件夹。(不要设置`sidebar: false`)
> - 参照下面的示例配置好front matter,然后就可以在导航栏或首页添加目录页链接了。
**示例**
```yaml
---
pageComponent: # 使用页面组件
name: Catalogue # 组件名:Catalogue => 目录页组件
data: # 组件所需数据
path: 01.学习笔记/01.前端 # 设置为`docs/`下面的某个文件夹相对路径,如‘01.学习笔记/01.前端’ 或 ’01.学习笔记‘ (有序号的要带序号)
imgUrl: /img/web.png # 目录页内的图片
description: JavaScript、ES6、Vue框架等前端技术 # 目录描述(可加入a标签)
title: 前端 # 页面标题
date: 2020-01-12 11:51:53 # 创建日期
permalink: /note/javascript # 永久链接
sidebar: false # 不显示侧边栏
article: false # 不是文章页 (不显示面包屑栏、最近更新栏等)
comment: false # 不显示评论栏
editLink: false # 不显示编辑按钮
---
```
::: tip
配置好目录页之后,点击文章页的面包屑将会跳转到目录页。
:::
## 配置项
### pageComponent.name
* 组件名,必需
* 使用目录页时 **必须** 设置为`Catalogue`
### pageComponent.data
* 组件所需数据,必需
### pageComponent.data.path
* 要为其生成目录页的文件夹的相对路径,必需
* 必须是在`docs`目录底下的文件夹相对路径
* 示例:`01.学习笔记``01.学习笔记/01.前端`(有序号的要带序号)
::: warning
`v1.8.2`版本之前,没有`path`属性,使用`key`代替。但`key`只支持指定`一级目录`的文件夹名称。
:::
### pageComponent.data.imgUrl
* 目录页内的图片,可选。(v1.9.4之前必填)
* 图片尺寸在页面中显示为80px*80px
### pageComponent.data.description
* 目录描述,必需
* 可加a标签(如需加入a标签时,标签内部的引号请使用单引号)
## 如何在导航栏中添加目录页链接
```js
// config.js
module.exports = {
themeConfig: {
nav: [
// 没有二级导航时可以直接添加
{text: '目录页', link: '/web/'},
// 有二级导航时
{text: '页面',
link: '/ui/', //目录页, vdoing主题新增的配置项,有二级导航时,可以点击一级导航跳到目录页
items: [
{text: 'HTML', link: '/pages/11/'},
{text: 'CSS', link: '/pages/22/'},
]
},
]
}
}
```
参考:[.vuepress/config/nav.js](https://github.com/xugaoyi/vuepress-theme-vdoing/blob/master/docs/.vuepress/config/nav.js)
@@ -0,0 +1,32 @@
---
title: 添加摘要
date: 2020-05-13 11:47:49
permalink: /pages/1cc523
article: false
---
文章摘要会显示于首页的详细版文章列表中,在编写文章时(`.md`文件中)在合适的位置添加一个`<!-- more -->`注释。注释前面的内容将会暴露在摘要中。
示例:
```md
# 如何根据系统主题自动响应CSS深色模式
![Dark](https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200427163531.jpg)
很多人喜欢选择APP或网站中的深色模式,也许他们更喜欢这样的外观,或者他们想让自己的眼睛免受疲劳。这篇文章将告诉你如何在网站中实现一个自动的CSS深色模式,根据访客的系统主题来自动响应。
<!-- more -->
## CSS 深色模式 (Dark Mode)
...
```
如示例的内容,在`<!-- more -->`注释前面的除了标题之外所有内容将显示到摘要中。摘要在文章列表的显示效果如下:
<p align="center">
<img src="https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200513165502.png" width="600">
</p>
值得注意的是摘要的内容也是文章内容的一部分,会显示在文章详情页中。
@@ -0,0 +1,85 @@
---
title: 修改主题颜色和样式
date: 2020-05-13 11:48:50
permalink: /pages/f51918
article: false
---
## 主题样式变量
下面是vdoing主题使用的css变量,你可以在`.vuepress/styles/palette.styl`修改这些变量覆盖它们:
```stylus
//***vdoing-CSS***//
//
$bannerTextColor = #fff // 首页banner区()
$accentColor = #11A8CD
$activeColor = #ff5722
$arrowBgColor = #ccc
//
$navbarHeight = 3.6rem
$sidebarWidth = 18rem
$contentWidth = 860px
$homePageWidth = 1100px
$rightMenuWidth = 230px //
//
$lineNumbersWrapperWidth = 2.5rem
//
.theme-mode-light
--bodyBg: #f4f4f4
--mainBg: rgba(255,255,255,1)
--sidebarBg: rgba(255,255,255,.8)
--blurBg: rgba(255,255,255,.9)
--textColor: #004050
--textLightenColor: #0085AD
--borderColor: rgba(0,0,0,.15)
//
--codeBg: #f6f6f6
--codeColor: #525252
codeThemeLight()
//
// --codeBg: #252526
// --codeColor: #fff
// codeThemeDark()
//
.theme-mode-dark
--bodyBg: rgb(39,39,43)
--mainBg: rgba(30,30,34,1)
--sidebarBg: rgba(30,30,34,.8)
--blurBg: rgba(30,30,34,.8)
--textColor: rgb(140,140,150)
--textLightenColor: #0085AD
--borderColor: #2C2C3A
--codeBg: #252526
--codeColor: #fff
codeThemeDark()
//
.theme-mode-read
--bodyBg: rgb(240,240,208)
--mainBg: rgba(245,245,213,1)
--sidebarBg: rgba(245,245,213,.8)
--blurBg: rgba(245,245,213,.9)
--textColor: #004050
--textLightenColor: #0085AD
--borderColor: rgba(0,0,0,.15)
--codeBg: #282c34
--codeColor: #fff
codeThemeDark()
```
上面的变量值可能不是最新的,最新的变量值可查看:[palette.styl](https://github.com/xugaoyi/vuepress-theme-vdoing/blob/master/theme-vdoing/styles/palette.styl)
## 样式修改与适配
当你想修改主题某个地方的样式时,或者你在给博客添加了一些新的模块或插件,发现样式和主题的样式不协调时,都可以在`.vuepress/styles/index.styl`
添加css样式来做修改。
需要注意的是,你在自己写的css样式中,请尽量使用主题提供的变量来进行适配。
> **小技巧**:当你发现自己写的css样式优先级没有原来的样式高时,可以在样式后面添加`!improtant`后缀,使你的样式优先级是最高的。
@@ -0,0 +1,22 @@
---
title: 评论栏
date: 2020-05-13 12:00:39
permalink: /pages/ce175c
article: false
---
评论栏推荐使用vuepress插件的方式来导入,这里推荐一些vuepress的评论插件,使用方法查阅相应的文档。
### vuepress-plugin-comment
* [vuepress-plugin-comment](https://github.com/dongyuanxin/vuepress-plugin-comment)
### vuepress-plugin-vssue
* [vuepress-plugin-vssue](https://vssue.js.org/)
### vuepress-plugin-vssue-global
* [vuepress-plugin-vssue-global](https://github.com/u2sb/vuepress-plugin-vssue-global)
### Twikoo
* [Twikoo](https://github.com/imaegoo/twikoo)
@@ -0,0 +1,208 @@
---
title: 资源
date: 2020-05-12 15:10:15
permalink: /pages/db78e2
article: false
---
## 插件推荐
* [vuepress-plugin-fulltext-search](https://github.com/leo-buneev/vuepress-plugin-fulltext-search) 全文搜索
* [vuepress-plugin-thirdparty-search](https://github.com/xugaoyi/vuepress-plugin-thirdparty-search) 可以添加第三方搜索链接的搜索框
* [vuepress-plugin-one-click-copy](https://www.npmjs.com/package/vuepress-plugin-one-click-copy) 代码块一键复制
* [vuepress-plugin-comment](https://github.com/dongyuanxin/vuepress-plugin-comment) 评论区
* [vuepress-plugin-vssue](https://vssue.js.org/) 评论区(单页)
* [vuepress-plugin-vssue-global](https://github.com/u2sb/vuepress-plugin-vssue-global) 评论区(全局)
* [vuepress-plugin-smplayer](https://github.com/u2sb/vuepress-plugin-smplayer) 播放器
* [vuepress-plugin-flowchart](https://www.npmjs.com/package/vuepress-plugin-flowchart) 流程图
* [vuepress-plugin-mathjax](https://www.npmjs.com/package/vuepress-plugin-mathjax) 数学公式
* [vuepress-plugin-tabs](https://www.npmjs.com/package/vuepress-plugin-tabs/) 选项卡
* [vuepress-plugin-element-ui](https://www.npmjs.com/package/vuepress-plugin-element-ui/) Element UI
* [花里胡哨的插件](https://moefyit.github.io/moefy-vuepress/) 鼠标点击特效、背景彩带、音乐播放器等花里胡哨的插件
**更多插件...**
- [Awesome VuePress](https://github.com/vuepressjs/awesome-vuepress)
- [在npm中搜索"vuepressplugin"](https://www.npmjs.com/search?q=vuepress%E2%80%93plugin)
## 社区优秀解决方案
### [1. 站点信息模块](https://notes.youngkbt.cn/about/website/info/)
在首页添加`站点信息模块`,效果:
![](https://fastly.jsdelivr.net/gh/Kele-Bingtang/static/img/%E5%85%B3%E4%BA%8E/%E5%85%B3%E4%BA%8E%E6%9C%AC%E7%AB%99/20220102230720.png)
在文章页添加`文章字数``阅读时间``浏览量`,效果:
![](https://fastly.jsdelivr.net/gh/Kele-Bingtang/static/img/%E5%85%B3%E4%BA%8E/%E5%85%B3%E4%BA%8E%E6%9C%AC%E7%AB%99/20220103180059.png)
### [2. 私密文章功能](https://notes.youngkbt.cn/about/website/private/)
当大家想要「云端备份」文章到博客时,又不希望别人看到,该功能能满足你。
### [3. 首页大图模块](https://notes.youngkbt.cn/about/website/index-big-img/)
喜欢首页大图模式的朋友可以参考此教程。
### [4. 优雅的全文搜索方案](https://wiki.eryajf.net/pages/dfc792/#%E5%89%8D%E8%A8%80)
## 文章管理和发布
使用 [此插件 src-sy-post-publisher](https://github.com/terwer/src-sy-post-publisher) 发布[思源笔记](https://b3log.org/siyuan/)的文章到Vuepress等平台。
## 图标&配图
说明:以下图标**非主题内置**,你可以选择喜欢的图标右键保存或到图标库下载。更多图标:[阿里图标库](https://www.iconfont.cn/home/index)
### 静态图标
<table class="icons-table">
<tbody>
<tr>
<td align="center" valign="middle">
<img src="/img/png/编程.png" width=60>
<p class="name">编程</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/服务器.png" width=60>
<p class="name">服务器</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/机器学习.png" width=60>
<p class="name">机器学习</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/计算机网络.png" width=60>
<p class="name">计算机网络</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/面向对象.png" width=60>
<p class="name">面向对象</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/软件开发.png" width=60>
<p class="name">软件开发</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/数据结构.png" width=60>
<p class="name">数据结构</p>
</td>
</tr><tr></tr>
<tr>
<td align="center" valign="middle">
<img src="/img/png/数据库.png" width=60>
<p class="name">数据库</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/思维导图.png" width=60>
<p class="name">思维导图</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/算法.png" width=60>
<p class="name">算法</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/网络技术.png" width=60>
<p class="name">网络技术</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/系统.png" width=60>
<p class="name">系统</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/系统分析.png" width=60>
<p class="name">系统分析</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/项目管理.png" width=60>
<p class="name">项目管理</p>
</td>
</tr><tr></tr>
</tbody>
</table>
<style>
.icons-table td{
padding: 1em;
}
.icons-table p.name{
font-size: 14px;
margin: 10px 0 0 0;
}
</style>
### 萌系图标
- [猫咪系列](https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.dc64b3430&cid=37776)
![](https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/maomi.51n7h2qwlv00.webp)</br>
- [数码宝贝](https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.dc64b3430&cid=38124)
![](https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/smbb.3h83bez4dka0.webp)
- [水果系列](https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.dc64b3430&cid=38124)
![](https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/QQ20220122-122349@2x.4oqggy71iso0.webp)
### 动态图标
<table class="icons-table">
<tbody>
<tr>
<td align="center" valign="middle">
<img src="/img/gif/猫1.gif" width=60>
<p class="name">猫1</p>
</td>
<td align="center" valign="middle">
<img src="/img/gif/猫2.gif" width=60>
<p class="name">猫2</p>
</td>
<td align="center" valign="middle">
<img src="/img/gif/猫3.gif" width=60>
<p class="name">猫3</p>
</td>
<td align="center" valign="middle">
<img src="/img/gif/猫4.gif" width=60>
<p class="name">猫4</p>
</td>
</tr><tr></tr>
</tbody>
</table>
### 插画
[免费插画](https://undraw.co/illustrations)</br>
[2D/3D/手绘插画](https://storytale.io/)</br>
[阿里插画库](https://www.iconfont.cn/illustrations/index)</br>
### 配图
[可画-文章配图](https://www.canva.cn/)
### Logo
[logo生成1](https://www.designevo.com/logo-maker/)</br>
[logo生成2](https://instantlogodesign.com/)
### Emoji表情
[Emoji百科](https://emojipedia.org/)
::: tip 小技巧
在任意输入框快速打开emoji表情方法:</br>
Windows系统下按<kbd>Win</kbd> + <kbd>.</kbd></br>
Mac系统下按<kbd>Control</kbd> + <kbd>Command</kbd> + <kbd>空格</kbd>
:::
## 共享资源
如果您有不错的资源,欢迎在 [资源分享区](https://github.com/xugaoyi/vuepress-theme-vdoing/discussions/categories/%E8%B5%84%E6%BA%90%E5%88%86%E4%BA%AB) 留言。
<br/>
<br/>
<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="/img/Apifox-860x320.png" alt="npm" class="no-zoom" style="width: 100%;border-radius: 2px;"></a>
@@ -0,0 +1,428 @@
---
title: 案例
date: 2020-05-14 11:39:45
permalink: /pages/5d571c
# sidebar: false
article: false
---
## 特别用户
::: cardImgList 3
```yaml
config:
imgHeight: 140px
data:
# - img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/[email protected]
# link: https://docs.openharmony.cn/pages/000000/
# name: OpenHarmony
# desc: 开放原子开源基金会
# author: OpenHarmony
# avatar: https://www.openharmony.cn/static/img/core-card-item2.a72a0d10.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/QQ20211215-144040.hgt2875r2zc.webp
link: https://baomidou.com/
name: MyBatis-Plus官网
desc: 🚀为简化开发而生
author: 青苗
avatar: https://baomidou.com/img/logo.svg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/[email protected]
link: https://docs.deepin.org
name: Deepin 社区文档
desc: Deepin 应用开发技术分享、DTK开发经验等
author: Deepin
avatar: https://fastly.jsdelivr.net/gh/xmuli/xmuliPic@pic/2021/deepin.png
- img: https://ks3-cn-beijing.ksyuncs.com/vform-static/img/vform_website.png
link: http://www.vform666.com
name: VForm官网
desc: 低代码表单优选方案,拖拽式设计,一键生成源码
author: vformAdmin
avatar: https://www.vform666.com/vform-logo.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/blog-gitalk-comment@master/img/xxx.7feub7n1y0g0.png
link: https://liteflow.yomahub.com
name: LiteFlow官网
desc: 轻量,快速,稳定可编排的组件式规则引擎
author: 铂赛东
avatar: https://portrait.gitee.com/uploads/avatars/user/367/1102362_bryan31_1578940308.png!avatar60
- img: https://fastly.jsdelivr.net/gh/xugaoyi/blog-gitalk-comment@master/img/176866696-743faf44-4afd-4c12-9728-f982ea885836.2205nb3vf5mo.webp
link: https://easy-es.cn/
name: Easy-Es官网
desc: 傻瓜级ElasticSearch搜索引擎ORM框架
author: 老汉
avatar: https://iknow.hs.net/9fa0407f-30ff-4d8b-82da-a4990e41a04b.png
```
:::
## 知识库兼博客
::: cardImgList 4
```yaml
config:
imgHeight: 140px
data:
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530114033.png
link: https://xugaoyi.com/
name: Evan's blog
desc: Web前端技术博客,积跬步以至千里,致敬每个爱学习的你。
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200727170555.jpg
link: https://miluluyo.github.io/vdoingBlog/
name: 麋鹿鲁哟
desc: 运气交给锦鲤,</br>你只管努力就好。 (●ˇ∀ˇ●)
author: 麋鹿鲁哟
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200122153807.jpg
- img: https://i.loli.net/2020/07/26/BUCplirGIq9YTNA.png
link: https://lingze.xyz/
name: Lingze's blog
desc: 少侠, 别来无恙?
author: 令则
avatar: https://i.loli.net/2020/07/11/XhqR7Idnk5LD8bC.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/[email protected]
link: https://www.xswsym.online/
name: summer's blog
desc: Devops运维技术博客,分享运维技术
author: 夏苏文
avatar: https://fastly.jsdelivr.net/gh/summerking1/image@main/tx.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20201110221457.png
link: https://gocifer.github.io
name: Gocifer's DB & Blog
desc: 一个中二少年,专注于计算机基础架构、云原生、网络、云计算的所学、所思、所行、所想。
author: gocifer.jay
avatar: https://gocifer.github.io/img/avatar/gocifer.png
- img: https://dra-m.com/images/thumbnail.png
link: https://dra-m.com/
name: Dra-M
desc: JAVA后端
author: 莫小龙
avatar: https://q1.qlogo.cn/g?b=qq&nk=975425198&s=640
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200905142134.png
link: https://www.coder163.com/
name: 跟着老侯玩编程
desc: 一个乐于编程知识分享的站点
author: 舞动的代码
avatar: https://www.coder163.com/img/qun.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20210119213748.jpg
link: https://haijunit.top/
name: 学习笔记
desc: 平时的技术积累|分享交流技术心得|温故而知新
author: 爱做梦的奋斗青年
avatar: https://haijunit.top/images/avatar.png
- img: https://fastly.jsdelivr.net/gh/yxw839841231/images/studying-icu/20210120110320.png
link: https://www.studying.icu/
name: 研究院
desc: 一万年太久,只争朝夕
author: xwyang
avatar: https://avatars1.githubusercontent.com/u/13757119?s=80&v=4
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/[email protected]
link: https://blog.kimen.com.cn/
name: Kimen's Blog
desc: 全沾攻城狮
author: Kimen
avatar: https://avatars.githubusercontent.com/u/25970284?s=460&u=69b419ad6de33eaa1d6b73d7f065f710076d6c55&v=4
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/QQ20211101-165045-(1).4b4axinii160.png
link: https://lixianglong3210.gitee.io/xlong-vdoing/
name: XLONG's blog
desc: 蒸汽波、咖啡
author: lixianglong3210
avatar: http://www.lixianglong.cn/bg_store/img/avatar/default.jpg
- img: https://fastly.jsdelivr.net/gh/Awrtiger/mirrorfile/img/web.png
link: https://www.ool.cool/
name: 偷吃了鸡蛋的梨
desc: 捣鼓这,捣鼓那。
author: Awrtiger
avatar: https://fastly.jsdelivr.net/gh/Awrtiger/mirrorfile/img/avatar.jpg
- img: https://image-1302577725.cos.ap-beijing.myqcloud.com/img/20210402183053.png
link: https://f4de-bak.github.io/
name: Xinghai's Blog
desc: Web Security | Java Security
author: Xinghai
avatar: https://image-1302577725.cos.ap-beijing.myqcloud.com/img/20210328234543.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/eyjf.png
link: https://wiki.eryajf.net
name: 二丫讲梵
desc: 学习,记录,分享。(运维生活编程)
author: 二丫讲梵
avatar: https://wiki.eryajf.net/img/logo.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/ss.2tev477ruza0.png
link: https://masongsong.cn
name: 松松的一尺三分地
desc: 记录工作和学习中的所得
author: masongsong
avatar: https://fastly.jsdelivr.net/gh/MssText/learn@master/images/49203535.35emn5vryma0.png
- img: https://fastly.jsdelivr.net/gh/Kele-Bingtang/static/user/20211218235045.png
link: https://notes.youngkbt.cn/
name: Young Kbt Blog
desc: 记录学习Java, Web, 框架, 工具, 前端等相关知识, 记录生活和技术路程, 分享编程技巧。
author: Ship Liu
avatar: https://fastly.jsdelivr.net/gh/Kele-Bingtang/static/user/avatar2.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/123.57gcfayi85k0.webp
link: https://blog.dragon1573.wang/
name: 断桥烟雨の学习笔记
desc: 学习爱我,我爱学习~
author: べ断桥烟雨ミ
avatar: https://avatars.githubusercontent.com/u/49941141
- img: https://fastly.jsdelivr.net/gh/xugaoyi/blog-gitalk-comment@master/img/20220210094650.3q09d26tigc0.webp
link: https://chuck6.github.io/blog/
name: 梵一的博客
desc: 个人博客和知识分享
author: 梵一
avatar: https://chuck6.github.io/blog/img/paizhao.jpg
- img: https://fastly.jsdelivr.net/gh/niumoo/cdn-assets/2021/20220316145528.png
link: https://www.wdbyte.com
name: 未读代码
desc: Java 开发知识库,分享原创文章
author: 程序猿阿朗
avatar: https://avatars.githubusercontent.com/u/26371673?v=4
- img: https://fastly.jsdelivr.net/gh/simonzhangs/image-hosting@master/vue-plugin-example/blog.qg2buhe5h4g.webp
link: https://simonzhangs.github.io/
name: 松本松的博客儿
desc: Web前端技术博客,积跬步以至千里。
author: simonzhangs
avatar: https://fastly.jsdelivr.net/gh/simonzhangs/image-hosting@master/20220319/image.4x708q9wzse0.webp
- img: https://fastly.jsdelivr.net/gh/terwer/upload/img/image-20220422000045653.png
link: http://terwergreen.com
name: 远方的灯塔
desc: 专注于服务端技术分享
author: terwer
avatar: https://fastly.jsdelivr.net/gh/terwer/upload/img/photo.jpg
- img: https://fastly.jsdelivr.net/gh/nksuya/image_store@main/suyablog_home.5iou2ogjrm80.webp
link: https://suyaspace.com/
name: Suya's blog
desc: 个人博客,分享技术文章,学习笔记,植物相关知识等。
author: Suya
avatar: https://fastly.jsdelivr.net/gh/nksuya/image_store@master/tech/avatar.2tycyyc1ebr4.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store2@master/img/[email protected]
link: https://colorpanda.aifan.jp/
name: ColorPanda
desc: 日语中文英文学习网站
author: ColorPanda
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store2@master/img/image.3qr8m501tl20.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/blog-gitalk-comment@master/img/asdf.23jsekfejla8.jpeg
link: https://inannan423.github.io/
name: Zihanio
desc: Zihan的学习博客
author: Zihan
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/blog-gitalk-comment@master/img/xxx.67b3ygadagk0.jpeg
- img: https://user-images.githubusercontent.com/866409/175853573-28ded198-2348-4a82-8ddd-05088161e3fe.png
link: https://xingcxb.com
name: 不器小窝
desc: 但知行好事,莫要问前程
author: 不器
avatar: https://avatars.githubusercontent.com/u/866409?v=4
- img: https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/QQ20220722-141037.22uk9ow7ary.png
link: https://eryajf.github.io/vdoing-template/
name: Vdoing主题博客模板
desc: Vdoing主题博客模板
author: eryajf
avatar: https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/33259379.277tur21ir40.jpeg
- img: https://cdn.jsdelivr.net/gh/FireHH/github_img_repository/logo/微信截图_20220810164903.png
link: https://javaessay.cn/
name: Java essay
desc: Java散文知识库,Spring全家桶,SpringCloud全家桶,高可用高并发架构,面试等
author: Mr.Fire
avatar: https://cdn.jsdelivr.net/gh/FireHH/github_img_repository/logo/huge.jpg
- img: https://chendapeng.cn/images/about/blog_image.png
link: https://chendapeng.cn
name: 行百里er
desc: Java,个人技术博客,后端开发,技术架构,分布式技术,Spring Cloud Alibaba,Elasticsearch,Redis,算法,数据结构,Git
author: 行百里er
avatar: https://chendapeng.cn/images/about/avatar.png
- img: http://xyhwh-nav.cn/img/index.png
link: http://xyhwh-nav.cn
name: Captain
desc: 学习技术,Java基础、面试知识点、项目经验总结和一些学习笔记
author: Captain
avatar: http://xyhwh-nav.cn/img/logo.png
- img: https://xiaoxue-images.oss-cn-shenzhen.aliyuncs.com/blog/202212271108541.jpg
link: https://blog.xueqimiao.com/
name: 小薛博客
desc: 小薛博客,专注IT技术分享,助力人人成为架构师
author: xueqimiao
avatar: https://xiaoxue-images.oss-cn-shenzhen.aliyuncs.com/blog/202212271110209.png
- img: https://cdn.jsdelivr.net/gh/su-dd/cdn/博客/202301311644669.png
link: https://blog.addai.cn/
name: 呆呆的博客
desc: 个人博客
author: 呆呆
avatar: https://cdn.addai.cn/博客/网站使用/呆呆.webp
- img: https://raw.githubusercontent.com/jorgen-zhao/blog/master/images/snapshot.png
link: https://jorgen.website
name: Jorgen's blog
desc: 🚀个人知识库兼博客🚀
author: jorgen
avatar: https://jorgen.website/img/avatar.jpg
```
:::
## 知识库
::: cardImgList 4
```yaml
config:
imgHeight: 150px
data:
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530114035.png
link: https://xugaoyi.github.io/vdoing-demo-repository/
name: 知识库演示
desc: Vdoing主题演示-知识库
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20201219205536.jpg
link: https://wiki.router86.com/
name: X86软路由和NAS
desc: 记录X86软路由和NAS的一些知识
author: MonoLogueChi
avatar: https://blog.xxwhite.com/assets/img/avatar.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20201219205318.jpg
link: https://blog.northword.cn/dft-learning
name: 计算模拟学习笔记
desc: 《能源环境材料计算模拟方法》学习笔记,涉及密度泛函理论、第一性原理等和Materials Studio、VASP等的使用。
author: Northword
avatar: https://storage.live.com/items/28C1032A24A9C53B!25785?authkey=AHAx3GOYEKGqm8I
- img: https://singerwimg-1300001977.cos.accelerate.myqcloud.com/20211008/WccSrJ0s.png
link: https://repository.singerw.com
name: Singerw's Repository
desc: 技术的风花雪月之事,有个存档的地方,对于复盘,回忆,都是一个极好的方谭。
author: Singerw
avatar: https://singerwimg-1300001977.cos.accelerate.myqcloud.com/2021/09/20/76f29482ffc9b.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/QQ20220113-114927.5oha7j06c580.webp
link: https://hippox.cn
name: hippo4j
desc: 🔥 动态线程池框架,附带监控报警功能,轻量级的运维监控平台
author: Acmenlt
avatar: https://hippox.cn/img/web.png
- img: https://pics.landcover100.com/pics/624e63f4c82b8.png
link: https://www.gisrsdata.com
name: 地信遥感数据汇
desc: 解决目前地信遥感方向数据混杂,资源难以寻找的问题,构建一个地信遥感学习、讨论、交流的平台。
author: 锐多宝
avatar: https://pics.landcover100.com/pics/624e6469cbb8a.jpg
- img: https://img.de7v.com/img/site-pic.jpg
link: https://www.de7v.com
name: De7v
desc: 专注于安卓领域的技术传播
author: wresource
avatar: https://img.de7v.com/img/wresource.png
- img: https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/11123.1aljpnjyr074.png
link: https://eryajf.github.io/HowToStartOpenSource/
name: HowToStartOpenSource
desc: GitHub开源项目维护协作指南
author: eryajf
avatar: https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/33259379.277tur21ir40.jpeg
```
:::
## 社区类
:::cardImgList
```yaml
config:
imgHeight: 150px
data:
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200921220111.png
link: https://course.yiwiz.com/
name: 奕维投资教程站
desc: 股票投资
author: 奕维
avatar: https://course.yiwiz.com/img/logo.png
```
:::
## 博客类
::: cardImgList
```yaml
config:
imgHeight: 150px
data:
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530114034.png
link: https://xugaoyi.github.io/vdoing-demo-blog/
name: Vdoing's blog
desc: Vdoing主题演示-博客
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
- img: https://s4.ax1x.com/2022/02/25/bAjINF.png
link: https://fangweilong.github.io/
name: Teler的日常
desc: 好好学习,天天向上
author: Teler
avatar: https://s4.ax1x.com/2022/02/25/bAva8J.jpg
- img: https://user-images.githubusercontent.com/53399655/163007243-1b99b96b-cac3-49ca-9950-03a1e877a6d8.png
link: https://xustudyxu.github.io/
name: xustudyxu's Blog
desc: 一起学习编程!
author: xustudyxu
avatar: https://xustudyxu.github.io/img/01.png
```
:::
## 文档类
::: cardImgList 4
```yaml
config:
imgHeight: 150px
data:
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530114036.png
link: https://xugaoyi.github.io/vuepress-theme-vdoing-doc/
name: vdoing(本站)
desc: 🚀一款简洁高效的VuePress 知识管理&博客 主题
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/QQ20210925-124836@2x (1).6ogvf85maog0.png
link: https://justauth.plus/
name: JA Plus 开发者文档
desc: 一款开源的登录认证中间件
author: yadong,zhang
avatar: https://portrait.gitee.com/uploads/avatars/user/261/784199_yadong.zhang_1578932767.png!avatar200
- img: https://assets.imgradeone.com/docsdokimod-pv.png
link: https://docs.dokimod.cn
name: DokiMod 开发文档
desc: 为 DDLC Mod 开发提供的完善文档
author: imgradeone
avatar: https://docs.dokimod.cn/logo_128x128.png
# - img: http://yogoyun.oss-cn-beijing.aliyuncs.com/help/public/other/20200630125515.png
# link: http://help.yogoyun.com/
# name: 柚果云服务
# desc: 智能商业应用程序服务商。
# author: 柚果云服务
# avatar: http://yogoyun.oss-cn-beijing.aliyuncs.com/admin/console/logo.png
- img: https://jeesite.com/docs/img/docs.png
link: http://docs.jeesite.com
name: JeeSite 在线文档
desc: JeeSite 快速开发平台 - 在线文档
author: ThinkGem
avatar: https://jeesite.com/docs/img/logo.png
- img: https://fastly.jsdelivr.net/gh/dreamncn/picBed@master/uPic/2022_05_12_20_40_04_1652359204_1652359204449_9mVkaR.png
link: https://cleanphp.ankio.net/
name: CleanPHP
desc: CleanPHP开发者文档
author: ankio
avatar: https://fastly.jsdelivr.net/gh/dreamncn/picBed@master/uPic/2022_04_04_19_48_51_1649072931_1649072931346_h4BGpQ.jpg
- img: https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/28284d.56bkx9qvhc00.webp
link: https://jpom-docs.keepbx.cn/
name: Jpom
desc: 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件
author: 不忘初心
avatar: https://jpom-docs.keepbx.cn/images/jpom_logo.png
- img: https://wumei.live/kerwincui/document/raw/branch/master/images/img.png
link: https://wumei.live/doc/
name: 物美智能
desc: 物美智能开源物联网平台,简单易用,可用于搭建物联网平台以及二次开发和学习。适用于智能家居、智慧办公、智慧社区、农业监测、水利监测、工业控制等。
author: 随遇而安 / kerwincui
avatar: https://portrait.gitee.com/uploads/avatars/user/611/1834441_kerwincui_1581523162.png!avatar200
- img: https://pic.imgdb.cn/item/637c92ef16f2c2beb11293c7.jpg
link: https://trace-recorder.xusc.cn
name: trace-recorder官网
desc: 简单的, 可伸缩的, 高性能的跟踪记录仪
author: 蔡旺
avatar: https://pic.imgdb.cn/item/637c933516f2c2beb112ed43.jpg
```
:::
</br></br>
## 申请加入案例
::: tip 你想在这个页面展示你的站点吗?
欢迎使用Vdoing主题的小伙伴到 [**这里**](https://github.com/xugaoyi/vuepress-theme-vdoing/issues/new?assignees=&labels=&template=+join_case.md) 留下你的站点信息,你的站点将有机会出现在这个页面里~
:::
@@ -0,0 +1,48 @@
---
title: 问答
date: 2020-05-25 12:01:52
permalink: /pages/9cc27d
# sidebar: false
article: false
---
## 我是一个小白,想使用这个主题搭建博客(知识库)需要做哪些工作?
答:使用这个主题前需要你掌握下面这些知识:
* 掌握 [markdown](https://xugaoyi.com/pages/ad247c4332211551/)、[yaml](https://xugaoyi.com/pages/4e8444e2d534d14f/) 语法
* 会使用终端(命令行),会使用 git
* 会阅读文档、搜索文档
* 会[VuePress](https://vuepress.vuejs.org/zh/)的基本使用和默认主题的基本配置
以上知识都掌握之后,再查看本文档。你也可以运行我的主题项目,一边看代码,一边查看文档。主题项目内写了比较多的注释代码,方便你的学习和使用。
## clone 项目后需要修改哪些地方?
答:大致的修改流程是这样的:
1. 首先让项目正常的跑起来
2. 根据需求构建和替换 [docs/<结构化目录>](/pages/2f674a/) 的目录及内容
3. 根据需求修改 [config.js](/pages/a20ce8/) 配置
4. 修改 [首页配置](/pages/f14bdb/)
5. 修改 [主题颜色和样式](/pages/f51918/)(如果你想修改的话)
## 可减少项目冗余的地方有哪些?
答:在完成项目的搭建之后,对于没有使用到的一些文件和代码,可以看情况删减,可删减的地方有:
* 卸载`config.js`中未使用的插件依赖包
* 参照 [目录结构](/pages/2f674a/),删除未使用到的 `可选` 文件
* 删除`.vusepress`目录内未使用到的 `可选` 文件
* 删除各文件内的注释代码
## 我可以不使用永久链接吗?
答:**不可以**。当你没有在front matter指定永久链接时,主题会[自动生成永久链接](/pages/088c16/)到front matter,你可以修改永久链接的值。
使用永久链接是出于以下几点考虑:
1.`config.js`配置nav时使用永久链接,就不会因为文件的路径或名称的改变而改变。
2. 对于博客而言,当别人收藏了你的文章,在未来的时间里都可以通过永久链接来访问到。
3. 主题中的`目录页`需要通过永久链接来访问文章。
## 是否支持国际化?
答:暂不支持。目前的国际化方案,只能是使用不同项目来处理,比如:一个项目是中文,一个项目是英文,在导航中添加中英文站点的链接。
## 打包后在本地预览怎么没有样式?
答:因vuepress打包后的CSS、JS等资源链接采用绝对路径引入,在本地预览时的引入链接是不正确的,部署到服务器即可正常引入和预览。如果你确实需要本地预览,可以使用[vuepress-plugin-serve](https://vuepress.github.io/zh/plugins/serve/)插件,使用方法看相应文档。
## 在`build`时出现错误,但是在`dev`时是正常的
答:这种情况多半是因为你在`created``beforeCreate`生命周期调用了浏览器API的原因导致,详情查看:[浏览器的 API 访问限制](https://vuepress.vuejs.org/zh/guide/using-vue.html#%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84-api-%E8%AE%BF%E9%97%AE%E9%99%90%E5%88%B6)
@@ -0,0 +1,51 @@
---
title: '支持这个项目'
date: 2020-05-12 15:09:57
permalink: /pages/1b12ed
sidebar: false
article: false
---
如果您正在使用这个项目并感觉良好,或者是想支持我继续开发,您可以通过如下`任意`方式支持我:
1. Star并分享 [vuepress-theme-vdoing](https://github.com/xugaoyi/vuepress-theme-vdoing) :rocket:
2. 轻轻点击一次上面的广告 :D
2. 进入下面赞助商花十几秒钟扫码注册一下, 赞助商就会代您赞助一笔小钱。 :D
3. 通过以下二维码 一次性捐款,打赏作者一杯茶。:tea:
谢谢! :heart:
| 微信赞赏 | 微信 | 支付宝 |
| :---: | :---: | :---: |
| <img :src="$withBase('/img/qrcode/wxzs.jpg')" alt="赞赏码" width=180> | <img :src="$withBase('/img/qrcode/wxzz.jpg')" alt="Wechat QRcode" width=180>| <img :src="$withBase('/img/qrcode/zfbzz.jpg')" alt="Alipay QRcode" width=180> |
## 赞助商
> 如果您想支持本项目,奈何囊中羞涩,您可以花十几秒点击下方赞助商进入注册(只需微信扫码就可以),赞助商就会代您赞助一笔小钱。
<!-- <a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="/img/Apifox-860x320.png" class="no-zoom" style="width: 400px;border-radius: 2px;"></a> -->
<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/431669861564_.2470ykdcpbds.jpg" class="no-zoom" style="border-radius: 2px;"></a>
## 成为赞助商
成为赞助商,即可将您的品牌LOGO及标语同时展示在:
- GitHub仓库主页顶部
- 官网首页顶部
- 官网所有文档页左侧边栏顶部
**主题官网平均每月约8w+浏览量 + GitHub仓库每月约1w+浏览量**
[联系方式](https://xugaoyi.com/about/#%E8%81%94%E7%B3%BB)
## 公众号
`有趣研究社`是本人对各种有趣的、好玩的、沙雕的创意和想法以在线小网站或者文章的形式表达出来,比如:
- [小霸王游戏机](https://game.xugaoyi.com)
- [爱国头像生成器](https://avatar.xugaoyi.com/)
- [到账语音生成器](https://zfb.xugaoyi.com/)
还有更多好玩的等你去探索吧~
<img :src="$withBase('/img/qrcode/gzh.jpg')" style="width:180px;" />
## 致谢
感谢给予支持的朋友,您的支持是我前进的动力 🎉
@@ -0,0 +1,6 @@
---
archivesPage: true
title: 博客文章
permalink: /blog/
article: false
---
+281
View File
@@ -0,0 +1,281 @@
---
home: true
heroImage: /img/logo.png
heroText: Neutrino-Proxy
tagline: 🚀一个基于 netty 的、开源的 java 内网穿透项目
actionText: 开始使用 →
actionLink: /pages/a2f161/
bannerBg: none # auto => 网格纹背景(有bodyBgImg时无背景),默认 | none => 无 | '大图地址' | background: 自定义背景样式 提示:如发现文本颜色不适应你的背景时可以到palette.styl修改$bannerTextColor变量
features: # 可选的
- title: 安全
details: 内外网传输数据。
- title: 快速
details: 快速部署。
- title: 穿透力强
details: 穿透力。
# 文章列表显示方式: detailed 默认,显示详细版文章列表(包括作者、分类、标签、摘要、分页等)| simple => 显示简约版文章列表(仅标题和日期)| none 不显示文章列表
postList: none
---
<p align="center">
<a class="become-sponsor" href="/pages/1b12ed/">支持这个项目</a>
</p>
<style>
.become-sponsor {
padding: 8px 20px;
display: inline-block;
color: #11a8cd;
border-radius: 30px;
box-sizing: border-box;
border: 1px solid #11a8cd;
}
</style>
<br/>
<p align="center">
<a href="https://www.npmjs.com/package/vuepress-theme-vdoing" target="_blank"><img src="https://img.shields.io/npm/v/vuepress-theme-vdoing" alt="npm" class="no-zoom"></a>
<a href="https://www.npmjs.com/package/vuepress-theme-vdoing" target="_blank"><img src="https://img.shields.io/npm/dt/vuepress-theme-vdoing" alt="npm" class="no-zoom"></a>
<a href="https://gitee.com/dromara/neutrino-proxy" target="_blank"><img src='https://gitee.com/dromara/neutrino-proxy/badge/star.svg?theme=dark' alt='star' class="no-zoom"></img></a>
<a href="https://gitee.com/dromara/neutrino-proxy" target="_blank"><img src='https://gitee.com/dromara/neutrino-proxy/badge/fork.svg?theme=dark' alt='forks' class="no-zoom"></a>
</p>
<br/>
<!-- 注释掉
<p align="center" style="color: #999;">
赞助商 (进入注册为主题作者充电)
</p>
<p align="center">
<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/441669861566_.2bedplbm21hc.jpg" alt="npm" class="no-zoom" style="width: 300px;border-radius: 2px;"></a>
</p>-->
<!--
## 🎖特别用户
::: cardList 3
```yaml
# - name: OpenHarmony
# desc: 开放原子开源基金会
# link: https://docs.openharmony.cn/pages/000000/
# bgColor: '#f1f1f1'
# textColor: '#2A3344'
- name: MyBatis-Plus官网
desc: 🚀为简化开发而生
link: https://baomidou.com/
bgColor: '#f1f1f1'
textColor: '#2A3344'
- name: Deepin 社区
desc: Deepin 应用开发技术分享、DTK开发经验等
link: https://docs.deepin.org
bgColor: '#f1f1f1'
textColor: '#2A3344'
- name: VForm官网
desc: 低代码表单优选方案,拖拽式设计,一键生成源码
link: http://www.vform666.com
bgColor: '#f1f1f1'
textColor: '#2A3344'
```
:::
-->
<br/>
## 🎉上新推荐
* `v1.7.0`:项目重构、底层框架更换为Solon。
* `v1.6.4`:代理使用细节优化。
- 支持代理服务端用户(删除/禁用)、端口池(删除/禁用/启用)、License(删除/禁用/启用)、端口映射(新增/删除/禁用/启用)实时生效。
- 启动参数优化。
- 服务端静态资源服务支持缓存、gzip压缩,提升响应速度。
* `v1.6.0`:关于日志、报表、客户端配置等相关优化。
* `v1.5.0`:增加了服务端管理页面,用于维护license、端口映射。
* `v1.0.0`:上线啦~&nbsp; 第一个完整版本。
更多上新请查阅:[**更新日志**](https://gitee.com/dromara/neutrino-proxy/releases)
<br/>
<!-- ## ⚡️未来...
* `v1.5.0`:新增配置文件对TypeScript的支持,参考[config.ts](https://github.com/xugaoyi/vuepress-theme-vdoing/blob/master/docs/.vuepress/config.ts)。新增[标题标记](/pages/3216b0/#titletag)。
::: tip
期待 [VuePress v2.0](https://github.com/vuepress/vuepress-next) 以及 [VitePress](https://github.com/vuejs/vitepress) 的正式发布...
届时,VuePress 1.x 编译慢的缺点将得到极大的改善。我将会视情况把主题升级至 VuePress v2.0 或 VitePress。还希望大家多多 [:sparkling_heart:支持](/pages/1b12ed/) 哟,持续关注吧~
::: -->
<br/>
<!-- ## 💎 公众号
`有趣研究社`是本人对各种有趣的、好玩的、沙雕的创意和想法以在线小网站或者文章的形式表达出来,比如:
- [小霸王游戏机](https://game.xugaoyi.com)
- [爱国头像生成器](https://avatar.xugaoyi.com/)
- [到账语音生成器](https://zfb.xugaoyi.com/)
还有更多好玩的等你去探索吧~
::: center
<img src="https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/qrcode.zdqv9mlfc0g.jpg" style="width:190px;" />
:::
<br/> -->
## ⚡ 反馈与交流
在使用过程中有任何问题和想法,请给我提 [Issue](https://gitee.com/dromara/neutrino-proxy/issues)。
你也可以在Issue查看别人提的问题和给出解决方案。
或者加入我们的交流群:
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/0.4pp7r95mdai0.jpeg" class="no-zoom" style="width:120px;margin: 10px;">
<p>vdoing微信群(添加我微信备注"进群")</p>
</td>
<td align="center" valign="middle">
<img :src="$withBase('/img/qrcode/qqq.webp')" alt="群号: 694387113" class="no-zoom" style="width:120px;margin: 10px;">
<p>vdoing QQ群: 694387113</p>
</td>
</tr>
</tbody>
</table>
<br/>
## 🏗️添砖加瓦
### 🎋分支说明
neutrino-proxy主要的源码分为两个分支,功能如下:
| 分支 | 作用 |
|---|---------------------------------------------------------------|
| master | 主分支,不接收任何pr或修改 |
| feature/1.7.1 | 开发分支,默认为下个版本的SNAPSHOT版本,接受修改或pr |
### 🐞提供bug反馈或建议
提交问题反馈请说明正在使用环境以及相关问题
- [Gitee issue](https://gitee.com/dromara/neutrino-proxy/issues)
[//]: # (- [Github issue]&#40;https://github.com/dromara/hutool/issues&#41;)
### 🧬贡献代码的步骤
贡献代码注意事项:
1. 在Gitee或者Github上fork项目到自己的repofork,一定要把项目fork一份。
2. 把fork过去的项目也就是你的项目clone到你的本地
3. 同步feature/1.7.1最新代码
4. 修改代码
5. 开发完成后,不忙着提PR,再拉一遍最新代码,如果有冲突、解决冲突
6. commit后push到自己的库
7. 登录Gitee在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
8. 等待维护者合并
<br/>
## 📚 Dromara 成员项目
<p align="center">
<a href="https://gitee.com/dromara/TLog" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/tlog2.png" alt="一个轻量级的分布式日志标记追踪神器,10分钟即可接入,自动对日志打标签完成微服务的链路追踪" width="15%">
</a>
<a href="https://gitee.com/dromara/liteFlow" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/liteflow.png" alt="轻量,快速,稳定,可编排的组件式流程引擎" width="15%">
</a>
<a href="https://hutool.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hutool.jpg" alt="小而全的Java工具类库,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。" width="15%">
</a>
<a href="https://sa-token.dev33.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/sa-token.png" alt="一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!" width="15%">
</a>
<a href="https://gitee.com/dromara/hmily" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hmily.png" alt="高性能一站式分布式事务解决方案。" width="15%">
</a>
<a href="https://gitee.com/dromara/Raincat" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/raincat.png" alt="强一致性分布式事务解决方案。" width="15%">
</a>
</p>
<p align="center">
<a href="https://gitee.com/dromara/myth" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/myth.png" alt="可靠消息分布式事务解决方案。" width="15%">
</a>
<a href="https://cubic.jiagoujishu.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/cubic.png" alt="一站式问题定位平台,以agent的方式无侵入接入应用,完整集成arthas功能模块,致力于应用级监控,帮助开发人员快速定位问题" width="15%">
</a>
<a href="https://maxkey.top/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/maxkey.png" alt="业界领先的身份管理和认证产品" width="15%">
</a>
<a href="http://forest.dtflyx.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/forest-logo.png" alt="Forest能够帮助您使用更简单的方式编写Java的HTTP客户端" width="15%">
</a>
<a href="https://jpom.io/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/jpom.png" alt="一款简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件" width="15%">
</a>
<a href="https://su.usthe.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/sureness.png" alt="面向 REST API 的高性能认证鉴权框架" width="15%">
</a>
</p>
<p align="center">
<a href="https://easy-es.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/easy-es2.png" alt="傻瓜级ElasticSearch搜索引擎ORM框架" width="15%">
</a>
<a href="https://gitee.com/dromara/northstar" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/northstar_logo.png" alt="Northstar盈富量化交易平台" width="15%">
</a>
<a href="https://hertzbeat.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hertzbeat_brand.jpg" alt="易用友好的云监控系统" width="15%">
</a>
<a href="https://plugins.sheng90.wang/fast-request/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/fast-request.gif" alt="Idea 版 Postman,为简化调试API而生" width="15%">
</a>
<a href="https://www.jeesuite.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/mendmix.png" alt="开源分布式云原生架构一站式解决方案" width="15%">
</a>
<a href="https://gitee.com/dromara/koalas-rpc" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/koalas-rpc2.png" alt="企业生产级百亿日PV高可用可拓展的RPC框架。" width="15%">
</a>
</p>
<p align="center">
<a href="https://async.sizegang.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/gobrs-async.png" alt="配置极简功能强大的异步任务动态编排框架" width="15%">
</a>
<a href="https://dynamictp.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dynamic-tp.png" alt="基于配置中心的轻量级动态可监控线程池" width="15%">
</a>
<a href="https://www.x-easypdf.cn" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/x-easypdf.png" alt="一个用搭积木的方式构建pdf的框架(基于pdfbox" width="15%">
</a>
<a href="http://dromara.gitee.io/image-combiner" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/image-combiner.png" alt="一个专门用于图片合成的工具,没有很复杂的功能,简单实用,却不失强大" width="15%">
</a>
<a href="https://www.herodotus.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dante-cloud2.png" alt="Dante-Cloud 是一款企业级微服务架构和服务能力开发平台。" width="15%">
</a>
<a href="https://dromara.org/zh/projects/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dromara.png" alt="让每一位开源爱好者,体会到开源的快乐。" width="15%">
</a>
</p>
<!-- AD -->
<div class="wwads-cn wwads-horizontal page-wwads" data-id="136"></div>
<style>
.page-wwads{
width:100%!important;
min-height: 0;
margin: 0;
}
.page-wwads .wwads-img img{
width:80px!important;
}
.page-wwads .wwads-poweredby{
width: 40px;
position: absolute;
right: 25px;
bottom: 3px;
}
.wwads-content .wwads-text, .page-wwads .wwads-text{
height: 100%;
padding-top: 5px;
display: block;
}
</style>
+27
View File
@@ -0,0 +1,27 @@
{
"name": "theme-vdoing-doc",
"version": "1.0.0",
"scripts": {
"dev": "vuepress dev docs",
"build": "vuepress build docs",
"deploy": "bash deploy.sh",
"updateTheme": "yarn remove vuepress-theme-vdoing && rm -rf node_modules && yarn && yarn add vuepress-theme-vdoing -D",
"editFm": "node utils/editFrontmatter.js"
},
"license": "MIT",
"devDependencies": {
"dayjs": "^1.9.7",
"inquirer": "^7.1.0",
"json2yaml": "^1.1.0",
"vuepress": "1.9.2",
"vuepress-plugin-baidu-tongji": "^1.0.1",
"vuepress-plugin-demo-block": "^0.7.2",
"vuepress-plugin-fulltext-search": "^2.2.1",
"vuepress-plugin-one-click-copy": "^1.0.2",
"vuepress-plugin-thirdparty-search": "^1.0.2",
"vuepress-plugin-zooming": "^1.1.7",
"vuepress-theme-vdoing": "^1.12.3",
"yamljs": "^0.3.0"
},
"dependencies": {}
}
+14
View File
@@ -0,0 +1,14 @@
#批量添加和修改、删除front matter配置文件
# 需要批量处理的路径,docs文件夹内的文件夹 (数组。映射路径:docs/arr[0]/arr[1] ... )
path:
- docs # 第一个成员必须是docs
# 要删除的字段 (数组)
delete:
# - test
# - tags
# 要添加、修改front matter的数据 front matter中没有的数据则添加,已有的数据则覆盖)
data:
article: false
@@ -0,0 +1,92 @@
/**
* 批量添加和修改front matter ,需要配置 ./config.yml 文件。
*/
const fs = require('fs'); // 文件模块
const path = require('path'); // 路径模块
const matter = require('gray-matter'); // front matter解析器 https://github.com/jonschlinkert/gray-matter
const jsonToYaml = require('json2yaml')
const yamlToJs = require('yamljs')
const inquirer = require('inquirer') // 命令行操作
const chalk = require('chalk') // 命令行打印美化
const readFileList = require('./modules/readFileList');
const { type, repairDate} = require('./modules/fn');
const log = console.log
const configPath = path.join(__dirname, 'config.yml') // 配置文件的路径
main();
/**
* 主体函数
*/
async function main() {
const promptList = [{
type: "confirm",
message: chalk.yellow('批量操作frontmatter有修改数据的风险,确定要继续吗?'),
name: "edit",
}];
let edit = true;
await inquirer.prompt(promptList).then(answers => {
edit = answers.edit
})
if(!edit) { // 退出操作
return
}
const config = yamlToJs.load(configPath) // 解析配置文件的数据转为js对象
if (type(config.path) !== 'array') {
log(chalk.red('路径配置有误,path字段应该是一个数组'))
return
}
if (config.path[0] !== 'docs') {
log(chalk.red("路径配置有误,path数组的第一个成员必须是'docs'"))
return
}
const filePath = path.join(__dirname, '..', ...config.path); // 要批量修改的文件路径
const files = readFileList(filePath); // 读取所有md文件数据
files.forEach(file => {
let dataStr = fs.readFileSync(file.filePath, 'utf8');// 读取每个md文件的内容
const fileMatterObj = matter(dataStr) // 解析md文件的front Matter。 fileMatterObj => {content:'剔除frontmatter后的文件内容字符串', data:{<frontmatter对象>}, ...}
let matterData = fileMatterObj.data; // 得到md文件的front Matter
let mark = false
// 删除操作
if (config.delete) {
if( type(config.delete) !== 'array' ) {
log(chalk.yellow('未能完成删除操作,delete字段的值应该是一个数组!'))
} else {
config.delete.forEach(item => {
if (matterData[item]) {
delete matterData[item]
mark = true
}
})
}
}
// 添加、修改操作
if (type(config.data) === 'object') {
Object.assign(matterData, config.data) // 将配置数据合并到front Matter对象
mark = true
}
// 有操作时才继续
if (mark) {
if(matterData.date && type(matterData.date) === 'date') {
matterData.date = repairDate(matterData.date) // 修复时间格式
}
const newData = jsonToYaml.stringify(matterData).replace(/\n\s{2}/g,"\n").replace(/"/g,"") + '---\r\n' + fileMatterObj.content;
fs.writeFileSync(file.filePath, newData); // 写入
log(chalk.green(`update frontmatter${file.filePath} `))
}
})
}
@@ -0,0 +1,21 @@
// 类型判断
exports.type = function (o){
var s = Object.prototype.toString.call(o)
return s.match(/\[object (.*?)\]/)[1].toLowerCase()
}
// 修复date时区格式的问题
exports.repairDate = function (date) {
date = new Date(date);
return `${date.getUTCFullYear()}-${zero(date.getUTCMonth()+1)}-${zero(date.getUTCDate())} ${zero(date.getUTCHours())}:${zero(date.getUTCMinutes())}:${zero(date.getUTCSeconds())}`;
}
// 日期的格式
exports.dateFormat = function (date) {
return `${date.getFullYear()}-${zero(date.getMonth()+1)}-${zero(date.getDate())} ${zero(date.getHours())}:${zero(date.getMinutes())}:${zero(date.getSeconds())}`
}
// 小于10补0
function zero(d){
return d.toString().padStart(2,'0')
}
@@ -0,0 +1,43 @@
/**
* 读取所有md文件数据
*/
const fs = require('fs'); // 文件模块
const path = require('path'); // 路径模块
const docsRoot = path.join(__dirname, '..', '..', 'docs'); // docs文件路径
function readFileList(dir = docsRoot, filesList = []) {
const files = fs.readdirSync(dir);
files.forEach( (item, index) => {
let filePath = path.join(dir, item);
const stat = fs.statSync(filePath);
if (stat.isDirectory() && item !== '.vuepress') {
readFileList(path.join(dir, item), filesList); //递归读取文件
} else {
if(path.basename(dir) !== 'docs'){ // 过滤docs目录级下的文件
const fileNameArr = path.basename(filePath).split('.')
let name = null, type = null;
if (fileNameArr.length === 2) { // 没有序号的文件
name = fileNameArr[0]
type = fileNameArr[1]
} else if (fileNameArr.length === 3) { // 有序号的文件
name = fileNameArr[1]
type = fileNameArr[2]
} else { // 超过两个‘.’的
log(chalk.yellow(`warning: 该文件 "${filePath}" 没有按照约定命名,将忽略生成相应数据。`))
return
}
if(type === 'md'){ // 过滤非md文件
filesList.push({
name,
filePath
});
}
}
}
});
return filesList;
}
module.exports = readFileList;
+6 -8
View File
@@ -1,10 +1,8 @@
# 功能点
- 用户流量报表
- 弹框展示月度明细
- 弹框展示今日流量明细
- License流量报表
- 弹框展示月度明细
- 弹框展示今日流量明细
- [x] 用户流量报表
- [ ] 兼容sqlite
- [x] License流量报表
- [ ] 兼容sqlite
- 首页图表📈
- 1、License在线数
- 2、端口映射在线数
@@ -33,11 +31,11 @@
- 今日流量折线图(上行、下行、总流量,按分钟统计0~24小时)
# Bug
- windows环境下直接运行发布版的jar包,日志输出乱码
- 部份用户windows环境下启动客户端,扫描类个数为0个
- ~~部份用户windows环境下启动客户端,扫描类个数为0个~~
- 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象
# 2.x规划
- 全面重构:底层更换为Solon + Mybatis Plus
- [x] 全面重构:底层更换为Solon + Mybatis Plus
- 规范协议:代理协议规范化,方便后续更好扩展、支持不同语言客户端接入
- 端口池优化:支持为license设置独占端口。方便后续开发jetbrains插件、solon插件
- 精细化控制:支持针对用户限速、限流