Merge remote-tracking branch 'origin/feature/1.6.2'

This commit is contained in:
aoshiguchen
2023-02-03 10:29:14 +08:00
62 changed files with 1636 additions and 78 deletions
+1
View File
@@ -5,6 +5,7 @@ data.db*
.neutrino-proxy.license
.neutrino-proxy-client.json*
lib/*
SensitiveInformation.txt
# Log file
+39
View File
@@ -0,0 +1,39 @@
#!/bin/sh
# 中微子代理客户端启动脚本,基础参数请自行修改
JAVA_OPS="-server -Xms256m -Xmx512m -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/$NAME/heapError/"
export JAVA_HOME=/work/programs/jdk/jdk1.8.0_171
export PATH=:$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
mkdir -p /work/$NAME/heapError/
NAME=neutrino-proxy-client
WORK=/work/projects
OUT=$WORK/$NAME/$NAME.out
JAR_PATH=$WORK/$NAME
function start(){
PID=`jps -l | grep $NAME.jar | cut -d' ' -f 1`
if [ -n "$PID" ]; then
echo "kill pid : $PID"
kill $PID
fi
sleep 3
if [ -n "$PID" ]; then
echo "kill fail & kill -9 pid : $PID"
kill -9 $PID
fi
if [ -f "$OUT" ]; then
time=$(date "+%Y%m%d-%H%M%S")
cp $OUT $JAR_PATH/logs/back_$time.out
fi
rm -f $OUT
cd $JAR_PATH
nohup java $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
echo "sleep 15s wating service start"
sleep 15
tail -200 $OUT
echo "start ${JAR_PATH} success ;log path:$OUT"
}
start
+40
View File
@@ -0,0 +1,40 @@
#!/bin/sh
# 中微子代理服务端启动脚本,基础参数请自行修改
JAVA_OPS="-server -Xms256m -Xmx1024m -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/$NAME/heapError/"
export JAVA_HOME=/work/programs/jdk/jdk1.8.0_171
export PATH=:$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
mkdir -p /work/$NAME/heapError/
NAME=neutrino-proxy-server
WORK=/work/projects
OUT=$WORK/$NAME/$NAME.out
JAR_PATH=$WORK/$NAME
function start(){
PID=`jps -l | grep $NAME.jar | cut -d' ' -f 1`
if [ -n "$PID" ]; then
echo "kill pid : $PID"
kill $PID
fi
sleep 3
if [ -n "$PID" ]; then
echo "kill fail & kill -9 pid : $PID"
kill -9 $PID
fi
if [ -f "$OUT" ]; then
time=$(date "+%Y%m%d-%H%M%S")
cp $OUT $JAR_PATH/logs/back_$time.out
fi
rm -f $OUT
cd $JAR_PATH
nohup java $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
echo "sleep 15s wating service start"
sleep 15
tail -200 $OUT
echo "start ${JAR_PATH} success ;log path:$OUT"
}
start
+1 -1
View File
@@ -31,7 +31,7 @@
- 1.3、服务端通过`CmdChannel`接收到来自客户端的`Auth`指令。若验证`license`有效,则建立`licenseId``CmdChannel`的映射缓存、
外网端口与`CmdChannel`的映射缓存。并启动服务端代理端口,等待用户连接。
## 2、用户连接阶段
- 2.1、用户访向服务端代理的外网端口发起请求,服务端建立`VisitorChannel`
- 2.1、用户向服务端代理的外网端口发起请求,服务端建立`VisitorChannel`
- 2.2、根据外网端口查找`CmdChannel`,若不存在有效的`CmdChannel`,则关闭该`VisitorChannel`。否则,
设置`VisitorChannel`为不可读,并携带内网映射信息(如:`127.0.0.1:3306`)通过`CmdChannel`向客户端发送`Connect`指令。
## 3、实际被代理服务连接阶段
@@ -171,6 +171,7 @@ public class AsgcCompiler {
/**
* 编译代码
* @param pkg 包名
* @param className 类名
* @param sourceCode 源代码
*/
@@ -145,6 +145,19 @@ public class DateUtil {
return cal.getTime();
}
/**
* 获取该日期当月第一天
*
* @param date 日期
* @return 结果日期
*/
public static Date getMonthBegin(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(getDayBegin(date));
calendar.set(Calendar.DAY_OF_MONTH, 1);
return calendar.getTime();
}
/**
* 获取该日期当月最后一天
*
@@ -153,10 +166,10 @@ public class DateUtil {
*/
public static Date getMonthEnd(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(getDayEnd(date));
calendar.setTime(getDayBegin(date));
calendar.add(Calendar.MONTH, 1);
calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.add(Calendar.DAY_OF_MONTH, -1);
calendar.add(Calendar.MILLISECOND, -1);
return calendar.getTime();
}
@@ -0,0 +1,88 @@
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
const devWebpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// cheap-module-eval-source-map is faster for development
devtool: config.dev.devtool,
// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: true,
hot: true,
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll,
}
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/local.env')
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
favicon: resolve('favicon.ico'),
title: 'vue-element-admin',
path: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
}),
]
})
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})
+1 -1
View File
@@ -1,5 +1,5 @@
module.exports = {
NODE_ENV: '"development"',
ENV_CONFIG: '"dev"',
BASE_API: '"http://localhost:8888"'
BASE_API: '"http://103.163.47.16:8888"'
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
NODE_ENV: '"development"',
ENV_CONFIG: '"local"',
BASE_API: '"http://localhost:8888"'
}
+3 -1
View File
@@ -6,7 +6,9 @@
"license": "MIT",
"private": true,
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --host=0.0.0.0",
"local": "webpack-dev-server --inline --progress --config build/webpack.local.conf.js",
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"build:local": "cross-env NODE_ENV=dev env_config=local node build/build.js",
"build:dev": "cross-env NODE_ENV=dev env_config=dev node build/build.js",
"build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js",
"build:sit": "cross-env NODE_ENV=production env_config=sit node build/build.js",
@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function fetchList(query) {
return request({
url: '/client-connect-record/page',
method: 'get',
params: query
})
}
+10
View File
@@ -51,3 +51,13 @@ export function deleteLicense(id) {
}
})
}
export function resetLicense(id) {
return request({
url: '/license/reset',
method: 'post',
params: {
id: id
}
})
}
+24
View File
@@ -73,3 +73,27 @@ export function hello() {
})
}
/**
* 管理员修改成员密码
* @param params
*/
export function updatePassword(params) {
return request({
url: '/user/update/password',
method: 'post',
data: params
})
}
/**
* 修改自己密码
* @param params
*/
export function updateUserPassword(params) {
return request({
url: '/user/current-user/update/password',
method: 'post',
data: params
})
}
+15 -4
View File
@@ -54,12 +54,14 @@ export default {
jobManager: '调度管理',
jobLog: '调度日志',
log: '日志管理',
loginLog: '登录日志'
loginLog: '登录日志',
clientConnectLog: '客户端连接日志'
},
navbar: {
logOut: '退出登录',
dashboard: '首页',
github: '项目地址',
updatePwd: '修改密码',
screenfull: '全屏',
theme: '换肤'
},
@@ -135,11 +137,20 @@ export default {
jobParam: '任务参数',
alarmEmail: '任务报警邮箱',
alarmDing: '任务报警钉钉',
jobLogCode: '执行结果',
jobLogMsg: '执行日志',
jobLogCode: '调度结果',
jobLogMsg: '调度日志',
jobLogTime: '调度时间',
alarmStatus: '报警状态',
ip: 'IP',
happendTime: '发生时间'
happendTime: '发生时间',
updatePwd: '修改密码',
msg: '消息',
outcome: '结果',
err: '异常信息',
resetKey: '重置Key'
},
button: {
lookOver: '查看'
},
errorLog: {
tips: '请点击右上角bug小图标',
+2 -1
View File
@@ -90,7 +90,8 @@ export const asyncRouterMap = [
},
children: [
{ path: 'jobLog', component: _import('log/jobLog'), name: 'jobLog', meta: { title: 'jobLog' }},
{ path: 'loginLog', component: _import('log/loginLog'), name: 'loginLog', meta: { title: 'loginLog' }}
{ path: 'loginLog', component: _import('log/loginLog'), name: 'loginLog', meta: { title: 'loginLog' }},
{ path: 'clientConnectLog', component: _import('log/clientConnectLog'), name: 'clientConnectLog', meta: { title: 'clientConnectLog' }}
]
}
]
@@ -30,7 +30,7 @@
.fixed-width{
.el-button--mini{
padding: 7px 10px;
width: 60px;
min-width: 60px;
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ service.interceptors.response.use(
type: 'error',
duration: 5 * 1000
})
if (res.code === 4 && !window.location.href.endsWith('#/login')) {
if ((res.code === 4 || res.code === 1) && !window.location.href.endsWith('#/login')) {
store.dispatch('FedLogOut').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
@@ -33,12 +33,17 @@
{{$t('navbar.github')}}
</el-dropdown-item>
</a>
<el-dropdown-item>
<span @click="updatePwdvisible = true">{{$t('navbar.updatePwd')}}</span>
</el-dropdown-item>
<el-dropdown-item divided>
<span @click="logout" style="display:block;">{{$t('navbar.logOut')}}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<UpdatePwd :visible="updatePwdvisible" @cancel="handleCancel"></UpdatePwd>
</el-menu>
</template>
@@ -50,9 +55,11 @@ import ErrorLog from '@/components/ErrorLog'
import Screenfull from '@/components/Screenfull'
import LangSelect from '@/components/LangSelect'
import ThemePicker from '@/components/ThemePicker'
import UpdatePwd from '../../system/components/update-pwd'
export default {
components: {
UpdatePwd,
Breadcrumb,
Hamburger,
ErrorLog,
@@ -68,6 +75,11 @@ export default {
'loginName'
])
},
data() {
return {
updatePwdvisible: false
}
},
methods: {
toggleSideBar() {
this.$store.dispatch('toggleSideBar')
@@ -76,6 +88,9 @@ export default {
this.$store.dispatch('LogOut').then(() => {
location.reload()// In order to re-instantiate the vue-router object to avoid bugs
})
},
handleCancel() {
this.updatePwdvisible = false
}
}
}
@@ -130,12 +145,12 @@ export default {
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
/*.el-icon-caret-bottom {
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}*/
}
}
}
@@ -0,0 +1,152 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<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.ip')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.ip}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.type')" min-width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.type | statusFilter">{{scope.row.type | statusName}}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.msg')" min-width="200" show-overflow-tooltip>
<template slot-scope="scope">
<span>{{scope.row.msg}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.outcome')" min-width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.code | statusFilter">{{scope.row.code | outcomeName}}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.err')" min-width="200" show-overflow-tooltip>
<template slot-scope="scope">
<span>{{scope.row.err}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.happendTime')" min-width="150">
<template slot-scope="scope">
<span>{{scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page.sync="listQuery.currentPage"
:page-sizes="[10,20,30, 50]" :page-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
<el-dialog
title="调度日志"
:visible.sync="dialogVisible"
width="700px"
:before-close="() => this.dialogVisible = false">
<div class="job-msg-div">{{selectRow.msg}}</div>
<div slot="footer" class="dialog-footer"></div>
</el-dialog>
</div>
</template>
<script>
import { fetchList } from '@/api/clientConnectLog'
import waves from '@/directive/waves' // 水波纹指令
export default {
name: 'clientConnectLog',
directives: {
waves
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: false,
listQuery: {
currentPage: 1,
pageSize: 10,
jobId: undefined
},
dialogVisible: false,
selectRow: {}
}
},
filters: {
statusName(status) {
const statusMap = {
1: '连接',
2: '断开'
}
return statusMap[status]
},
statusFilter(status) {
const statusMap = {
1: 'success',
2: 'danger'
}
return statusMap[status]
},
outcomeName(status) {
const statusMap = {
1: '成功',
2: '失败'
}
return statusMap[status]
}
},
created() {
this.getList()
},
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
})
},
handleFilter() {
this.listQuery.currentPage = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.pageSize = val
this.listQuery.currentPage = 1
this.getList()
},
handleCurrentChange(val) {
this.listQuery.currentPage = val
this.getList()
},
handleLookOver(row) {
this.selectRow = row
this.dialogVisible = true
}
}
}
</script>
<style>
.job-msg-div{
max-height: 400px;
overflow-y: auto;
}
</style>
+28 -6
View File
@@ -4,7 +4,7 @@
<el-select v-model="listQuery.jobId" placeholder="请选择" clearable>
<el-option v-for="item in jobList" :key="item.id" :label="item.desc" :value="item.id"/>
</el-select>
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
<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%">
@@ -24,9 +24,9 @@
<el-tag :type="scope.row.code | statusFilter">{{scope.row.code | statusName}}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.jobLogMsg')" min-width="400">
<el-table-column align="center" :label="$t('table.jobLogMsg')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.msg}}</span>
<el-button type="text" @click="handleLookOver(scope.row)">{{$t('button.lookOver')}}</el-button>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.alarmStatus')" min-width="120">
@@ -34,7 +34,7 @@
<el-tag :type="scope.row.alarmStatus | alarmStatusFilter">{{scope.row.alarmStatus | salarmStatusName}}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.createTime')" min-width="150">
<el-table-column align="center" :label="$t('table.jobLogTime')" min-width="150">
<template slot-scope="scope">
<span>{{scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
</template>
@@ -50,6 +50,15 @@
:page-sizes="[10,20,30, 50]" :page-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
<el-dialog
title="调度日志"
:visible.sync="dialogVisible"
width="700px"
:before-close="() => this.dialogVisible = false">
<div class="job-msg-div">{{selectRow.msg}}</div>
<div slot="footer" class="dialog-footer"></div>
</el-dialog>
</div>
</template>
@@ -74,7 +83,9 @@ export default {
pageSize: 10,
jobId: undefined
},
jobList: []
jobList: [],
dialogVisible: false,
selectRow: {}
}
},
filters: {
@@ -119,7 +130,6 @@ export default {
this.getJobList()
if (this.$route.query.jobId) {
this.listQuery.jobId = this.$route.query.jobId
console.log(this.listQuery.jobId, this.$route.query.jobId)
this.getList()
}
},
@@ -143,6 +153,7 @@ export default {
},
handleSizeChange(val) {
this.listQuery.pageSize = val
this.listQuery.currentPage = 1
this.getList()
},
handleCurrentChange(val) {
@@ -151,7 +162,18 @@ export default {
},
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>
@@ -121,6 +121,7 @@ export default {
},
handleSizeChange(val) {
this.listQuery.pageSize = val
this.listQuery.currentPage = 1
this.getList()
},
handleCurrentChange(val) {
@@ -47,14 +47,14 @@
<el-tag :type="scope.row.isOnline | statusFilter">{{scope.row.isOnline | isOnlineName}}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.actions')" width="230" class-name="small-padding fixed-width">
<el-table-column align="center" :label="$t('table.actions')" width="330" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">{{$t('table.edit')}}</el-button>
<el-button size="mini" type="primary" @click="handleReset(scope.row)">{{$t('table.resetKey')}}</el-button>
<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 size="mini" type="danger" @click="handleDelete(scope.row,'deleted')">{{$t('table.delete')}}</el-button>-->
<ButtonPopover @handleCommitClick="handleDelete2(scope.row)" style="margin-left: 10px"/>
</template>
</el-table-column>
</el-table>
@@ -98,7 +98,7 @@
</template>
<script>
import { fetchList, createLicense, updateLicense, updateEnableStatus, deleteLicense } from '@/api/license'
import { fetchList, createLicense, updateLicense, updateEnableStatus, deleteLicense, resetLicense } from '@/api/license'
import { userList } from '@/api/user'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
@@ -277,6 +277,25 @@
this.$refs['dataForm'].clearValidate()
})
},
handleReset(row) {
this.$confirm('确定重置吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
}).then(() => {
resetLicense(row.id).then(response => {
if (response.data.code === 0) {
this.$notify({
title: '成功',
message: '重置成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
}).catch(() => {})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
@@ -0,0 +1,121 @@
<template>
<div>
<el-dialog title="修改密码"
:visible.sync="visible"
:close-on-click-modal="false"
:before-close="onCancel"
:width="width"
>
<el-form :rules="rules" ref='form' :model="formData" label-position="left" label-width="80px" style='padding:0 20px'>
<el-form-item label="登录名" prop="loginName" v-if="row">
<el-input v-model="formData.loginName" disabled/>
</el-form-item>
<el-form-item label="旧密码" prop="oldLoginPassword" v-if="!row">
<el-input v-model="formData.oldLoginPassword" type="password" placeholder="请输入旧密码"/>
</el-form-item>
<el-form-item label="新密码" prop="loginPassword">
<el-input v-model="formData.loginPassword" type="password" placeholder="请输入新密码"/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword" v-if="!row">
<el-input v-model="formData.confirmPassword" type="password" placeholder="请输入确认密码"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="updatePwd">确定</el-button>
<el-button @click="onCancel">取消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { updatePassword, updateUserPassword } from '@/api/user'
export default {
name: 'updatePwd',
props: {
row: {
type: Object,
default: null
},
visible: Boolean,
width: {
type: String,
default: '500px'
}
},
data() {
const validatorValue = (rule, value, callback) => {
if (value === '' || !value) {
callback(new Error('确认密码必填'))
} else if (this.formData.loginPassword !== value) {
callback(new Error('与新密码不一致'))
} else {
callback()
}
}
return {
formData: {
id: undefined,
loginName: undefined,
loginPassword: undefined,
oldLoginPassword: undefined,
confirmPassword: undefined
},
rules: {
loginName: [
{ required: true, message: '用户名', trigger: 'blur' }
],
loginPassword: [
{ required: true, message: '新密码必填', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度为 6 到 10 位之间', trigger: 'blur' }
],
oldLoginPassword: [
{ required: true, message: '旧密码必填', trigger: 'blur' }
],
confirmPassword: [
{ required: true, validator: validatorValue, trigger: 'blur' }
]
}
}
},
watch: {
visible(val) {
if (val) {
this.row && this.getRow()
}
}
},
methods: {
getRow() {
this.formData.id = this.row.id
this.formData.loginName = this.row.loginName
this.formData.loginPassword = ''
},
updatePwd() {
this.$refs['form'].validate(valid => {
if (valid) {
if (this.row) {
updatePassword(this.formData).then(res => {
if (res.data.code === 0) {
this.$message.success('修改成功')
this.onCancel()
}
})
} else {
updateUserPassword(this.formData).then(res => {
if (res.data.code === 0) {
this.$message.success('修改成功')
this.onCancel()
}
})
}
}
})
},
onCancel() {
this.$emit('cancel')
}
}
}
</script>
+28 -8
View File
@@ -2,7 +2,7 @@
<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>
<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
@@ -37,14 +37,14 @@
<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="230" class-name="small-padding fixed-width">
<el-table-column align="center" :label="$t('table.actions')" :width="loginName === 'admin' ? 320 : 230" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">{{$t('table.edit')}}</el-button>
<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.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="loginName === 'admin'" type="warning" size="mini" @click="handleUpdatePassword(scope.row)">{{$t('table.updatePwd')}}</el-button>
<!-- <el-button size="mini" type="danger" @click="handleDelete(scope.row,'deleted')">{{$t('table.delete')}}</el-button>-->
<ButtonPopover @handleCommitClick="handleDelete2(scope.row)" style="margin-left: 10px"/>
</template>
</el-table-column>
</el-table>
@@ -56,7 +56,7 @@
</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 :rules="rules" ref="dataForm" :model="temp" label-position="left" label-width="70px" style='width: 400px; margin-left:50px'>
<el-form-item :label="$t('用户名')" prop="name">
<el-input v-model="temp.name"></el-input>
</el-form-item>
@@ -81,6 +81,8 @@
</span>
</el-dialog>
<UpdatePwd :row="selectRow" :visible="updatePwdvisible" @cancel="handleCancel"></UpdatePwd>
</div>
</template>
@@ -89,6 +91,8 @@
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
import UpdatePwd from './components/update-pwd'
import { mapGetters } from 'vuex'
const calendarTypeOptions = [
{ key: 'CN', display_name: 'China' },
@@ -108,8 +112,14 @@
directives: {
waves
},
computed: {
...mapGetters([
'loginName'
])
},
components: {
ButtonPopover
ButtonPopover,
UpdatePwd
},
data() {
return {
@@ -150,7 +160,9 @@
name: [{ required: true, message: '用户名必填', trigger: 'blur' }],
loginName: [{ required: true, message: '登录名必填', trigger: 'blur' }]
},
downloadLoading: false
downloadLoading: false,
updatePwdvisible: false,
selectRow: null
}
},
filters: {
@@ -327,6 +339,14 @@
return v[j]
}
}))
},
handleUpdatePassword(row) {
this.selectRow = row
this.updatePwdvisible = true
},
handleCancel() {
this.updatePwdvisible = false
this.selectRow = null
}
}
}
+5
View File
@@ -30,6 +30,11 @@
<artifactId>druid</artifactId>
<version>1.1.24</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
</dependencies>
<!-- <build>-->
@@ -26,8 +26,11 @@ import com.google.common.collect.Lists;
import fun.asgc.neutrino.core.annotation.PreLoad;
import fun.asgc.neutrino.core.db.template.JdbcTemplate;
import fun.asgc.neutrino.core.util.*;
import fun.asgc.neutrino.proxy.server.base.rest.config.SqliteConfig;
import fun.asgc.neutrino.proxy.server.base.rest.config.DbConfig;
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.sqlite.SQLiteConfig;
import org.sqlite.SQLiteDataSource;
import java.sql.DriverManager;
import java.util.List;
@@ -41,11 +44,18 @@ import java.util.List;
@PreLoad("init")
public class DBInitialize {
private static List<String> initDataTableNameList = Lists.newArrayList("user", "license", "port_pool", "port_mapping", "job_info");
private static SqliteConfig sqliteConfig;
private static DbConfig dbConfig;
private static JdbcTemplate jdbcTemplate;
private static DbTypeEnum dbTypeEnum;
public static void init() throws Exception {
sqliteConfig = ConfigUtil.getYmlConfig(SqliteConfig.class);
dbConfig = ConfigUtil.getYmlConfig(DbConfig.class);
Assert.notNull(dbConfig.getType(), "neutrino.data.db.type不能为空!");
dbTypeEnum = DbTypeEnum.of(dbConfig.getType());
Assert.notNull(dbTypeEnum, "neutrino.data.db.type取值异常!");
log.info("{}数据库初始化...", dbConfig.getType());
jdbcTemplate = getJdbcTemplate();
initDBStructure();
initDBData();
@@ -55,7 +65,7 @@ public class DBInitialize {
* 初始化数据库结构
*/
private static void initDBStructure() throws Exception {
List<String> lines = FileUtil.readContentAsStringList("classpath:/sql/init-structure.sql");
List<String> lines = FileUtil.readContentAsStringList(String.format("classpath:/sql/%s/init-structure.sql", dbConfig.getType()));
if (CollectionUtil.isEmpty(lines)) {
return;
}
@@ -87,7 +97,7 @@ public class DBInitialize {
if (count > 0) {
continue;
}
List<String> lines = FileUtil.readContentAsStringList(String.format("classpath:/sql/%s.data.sql", tableName));
List<String> lines = FileUtil.readContentAsStringList(String.format("classpath:/sql/%s/%s.data.sql", dbConfig.getType(), tableName));
if (CollectionUtil.isEmpty(lines)) {
return;
}
@@ -116,15 +126,30 @@ public class DBInitialize {
() -> null == jdbcTemplate,
DBInitialize.class,
() -> {
Class.forName(sqliteConfig.getDriverClass());
//建立一个数据库名data.db的连接,如果不存在就在当前目录下创建之
DriverManager.getConnection(sqliteConfig.getUrl());
// 创建数据源
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(sqliteConfig.getUrl());
dataSource.setDriverClassName(sqliteConfig.getDriverClass());
// 创建jdbcTemplate
jdbcTemplate = new JdbcTemplate(dataSource);
if (DbTypeEnum.SQLITE == dbTypeEnum) {
//建立一个数据库名data.db的连接,如果不存在就在当前目录下创建之
DriverManager.getConnection(dbConfig.getUrl());
// 创建数据源
SQLiteDataSource dataSource = new SQLiteDataSource();
dataSource.setUrl(dbConfig.getUrl());
dataSource.setJournalMode(SQLiteConfig.JournalMode.WAL.getValue());
// 创建jdbcTemplate
jdbcTemplate = new JdbcTemplate(dataSource);
} else if (DbTypeEnum.MYSQL == dbTypeEnum) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(dbConfig.getDriverClass());
dataSource.setUrl(dbConfig.getUrl());
dataSource.setInitialSize(5);
dataSource.setMinIdle(5);
dataSource.setMaxActive(20);
dataSource.setMaxWait(60000);
dataSource.setPoolPreparedStatements(true);
dataSource.setUsername(dbConfig.getUsername());
dataSource.setPassword(dbConfig.getPassword());
// 创建jdbcTemplate
jdbcTemplate = new JdbcTemplate(dataSource);
}
},
() -> jdbcTemplate
);
@@ -24,6 +24,7 @@ package fun.asgc.neutrino.proxy.server.base.rest.config;
import fun.asgc.neutrino.core.annotation.Configuration;
import fun.asgc.neutrino.core.annotation.NonIntercept;
import fun.asgc.neutrino.core.annotation.Value;
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
import lombok.Data;
/**
@@ -33,8 +34,14 @@ import lombok.Data;
*/
@Data
@NonIntercept
@Configuration(prefix = "neutrino.data.sqlite")
public class SqliteConfig {
@Configuration(prefix = "neutrino.data.db")
public class DbConfig {
/**
* 数据库类型
* {@link DbTypeEnum}
*/
@Value("type")
private String type;
/**
* 连接url
*/
@@ -45,4 +52,16 @@ public class SqliteConfig {
*/
@Value("driver-class")
private String driverClass;
/**
* 用户名
*/
@Value("username")
private String username;
/**
* 密码
*/
@Value("password")
private String password;
}
@@ -21,9 +21,12 @@
*/
package fun.asgc.neutrino.proxy.server.base.rest.config;
import com.alibaba.druid.pool.DruidDataSource;
import fun.asgc.neutrino.core.annotation.*;
import fun.asgc.neutrino.core.base.Ordered;
import fun.asgc.neutrino.core.db.template.JdbcTemplate;
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
import org.sqlite.SQLiteConfig;
import org.sqlite.SQLiteDataSource;
import javax.sql.DataSource;
@@ -37,14 +40,31 @@ import javax.sql.DataSource;
@Component
public class RestConfiguration {
@Autowired
private SqliteConfig sqliteConfig;
private DbConfig dbConfig;
@Bean
public DataSource dataSource() {
SQLiteDataSource dataSource = new SQLiteDataSource();
dataSource.setUrl(sqliteConfig.getUrl());
dataSource.setJournalMode("WAL");
return dataSource;
DbTypeEnum dbTypeEnum = DbTypeEnum.of(dbConfig.getType());
if (DbTypeEnum.SQLITE == dbTypeEnum) {
SQLiteDataSource dataSource = new SQLiteDataSource();
dataSource.setUrl(dbConfig.getUrl());
dataSource.setJournalMode(SQLiteConfig.JournalMode.WAL.getValue());
return dataSource;
} else if (DbTypeEnum.MYSQL == dbTypeEnum) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(dbConfig.getDriverClass());
dataSource.setUrl(dbConfig.getUrl());
dataSource.setInitialSize(5);
dataSource.setMinIdle(5);
dataSource.setMaxActive(20);
dataSource.setMaxWait(60000);
dataSource.setPoolPreparedStatements(true);
dataSource.setUsername(dbConfig.getUsername());
dataSource.setPassword(dbConfig.getPassword());
return dataSource;
}
return null;
}
@Bean
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* 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.
*/
package fun.asgc.neutrino.proxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 数据库类型美剧
* @author: aoshiguchen
* @date: 2022/11/25
*/
@Getter
@AllArgsConstructor
public enum DbTypeEnum {
SQLITE("sqlite"),
MYSQL("mysql");
private String type;
private static final Map<String, DbTypeEnum> cache = Stream.of(DbTypeEnum.values()).collect(Collectors.toMap(DbTypeEnum::getType, Function.identity()));
public static DbTypeEnum of(String type) {
return cache.get(type);
}
}
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* 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.
*/
package fun.asgc.neutrino.proxy.server.controller;
import fun.asgc.neutrino.core.annotation.Autowired;
import fun.asgc.neutrino.core.annotation.NonIntercept;
import fun.asgc.neutrino.core.db.page.Page;
import fun.asgc.neutrino.core.db.page.PageQuery;
import fun.asgc.neutrino.core.web.annotation.GetMapping;
import fun.asgc.neutrino.core.web.annotation.RequestMapping;
import fun.asgc.neutrino.core.web.annotation.RestController;
import fun.asgc.neutrino.proxy.server.controller.req.ClientConnectRecordListReq;
import fun.asgc.neutrino.proxy.server.controller.res.ClientConnectRecordListRes;
import fun.asgc.neutrino.proxy.server.service.ClientConnectRecordService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import lombok.extern.slf4j.Slf4j;
/**
* @author: aoshiguchen
* @date: 2022/11/26
*/
@Slf4j
@NonIntercept
@RequestMapping("client-connect-record")
@RestController
public class ClientConnectRecordController {
@Autowired
private ClientConnectRecordService clientConnectRecordService;
@GetMapping("page")
public Page<ClientConnectRecordListRes> page(PageQuery pageQuery, ClientConnectRecordListReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return clientConnectRecordService.page(pageQuery, req);
}
}
@@ -21,11 +21,20 @@
*/
package fun.asgc.neutrino.proxy.server.controller;
import fun.asgc.neutrino.core.annotation.Autowired;
import fun.asgc.neutrino.core.annotation.NonIntercept;
import fun.asgc.neutrino.core.db.page.Page;
import fun.asgc.neutrino.core.db.page.PageQuery;
import fun.asgc.neutrino.core.web.annotation.GetMapping;
import fun.asgc.neutrino.core.web.annotation.RequestMapping;
import fun.asgc.neutrino.core.web.annotation.RestController;
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.ReportDataViewRes;
import fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes;
import fun.asgc.neutrino.proxy.server.service.ReportService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
/**
* 报表管理
@@ -37,6 +46,9 @@ import fun.asgc.neutrino.proxy.server.controller.res.ReportDataViewRes;
@RestController
public class ReportController {
@Autowired
private ReportService reportService;
/**
* 数据一览:
* 在线用户数 查询token有效的用户ID数
@@ -59,4 +71,30 @@ public class ReportController {
.setTotalUpstreamFlow("23K").setTotalDownwardFlow("47M")
;
}
/**
* 用户流量报表分页
* @param pageQuery
* @param req
* @return
*/
@GetMapping("user/flow-report/page")
public Page<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return reportService.userFlowReportPage(pageQuery, req);
}
/**
* license流量报表分页
* @param pageQuery
* @param req
* @return
*/
@GetMapping("license/flow-report/page")
public Page<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return reportService.licenseFlowReportPage(pageQuery, req);
}
}
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* 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.
*/
package fun.asgc.neutrino.proxy.server.controller.req;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2022/11/26
*/
@Data
public class ClientConnectRecordListReq {
}
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* 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.
*/
package fun.asgc.neutrino.proxy.server.controller.req;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Data
public class LicenseFlowReportReq {
}
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* 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.
*/
package fun.asgc.neutrino.proxy.server.controller.req;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Data
public class UserFlowReportReq {
}
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* 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.
*/
package fun.asgc.neutrino.proxy.server.controller.res;
import lombok.Data;
import java.util.Date;
/**
* @author: aoshiguchen
* @date: 2022/11/26
*/
@Data
public class ClientConnectRecordListRes {
private Integer id;
private String ip;
private Integer licenseId;
private Integer type;
private Integer userId;
private String userName;
private String licenseName;
private String msg;
/**
* 1、成功
* 2、失败
*/
private Integer code;
private String err;
/**
* 创建时间
*/
private Date createTime;
}
@@ -0,0 +1,78 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* 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.
*/
package fun.asgc.neutrino.proxy.server.controller.res;
import lombok.Data;
import java.util.Date;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Data
public class LicenseFlowReportRes {
/**
* 用户ID
*/
private Integer userId;
/**
* 用户名称
*/
private String userName;
/**
* licenseId
*/
private Integer licenseId;
/**
* license名称
*/
private String licenseName;
/**
* 写入字节数
*/
private Long writeBytes;
/**
* 读取字节数
*/
private Long readBytes;
/**
* 写入流量描述
*/
private String writeFlowStr;
/**
* 读取流量描述
*/
private String readFlowStr;
/**
* 流量描述
*/
private String flowStr;
/**
* 报表时间
*/
private Date date;
/**
* 创建时间
*/
private Date createTime;
}
@@ -0,0 +1,78 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* 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.
*/
package fun.asgc.neutrino.proxy.server.controller.res;
import lombok.Data;
import java.util.Date;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Data
public class UserFlowReportRes {
/**
* 用户ID
*/
private Integer userId;
/**
* 用户名称
*/
private String userName;
/**
* 历史写入字节数
*/
private Long historyWriteBytes;
/**
* 历史读取字节数
*/
private Long historyReadBytes;
/**
* 写入字节数
*/
private Long writeBytes;
/**
* 读取字节数
*/
private Long readBytes;
/**
* 写入流量描述
*/
private String writeFlowStr;
/**
* 读取流量描述
*/
private String readFlowStr;
/**
* 流量描述
*/
private String flowStr;
/**
* 报表时间
*/
private Date date;
/**
* 创建时间
*/
private Date createTime;
}
@@ -2,7 +2,12 @@ package fun.asgc.neutrino.proxy.server.dal;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.aop.Intercept;
import fun.asgc.neutrino.core.db.annotation.ResultType;
import fun.asgc.neutrino.core.db.annotation.Select;
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
import fun.asgc.neutrino.core.db.page.Page;
import fun.asgc.neutrino.proxy.server.controller.req.ClientConnectRecordListReq;
import fun.asgc.neutrino.proxy.server.controller.res.ClientConnectRecordListRes;
import fun.asgc.neutrino.proxy.server.dal.entity.ClientConnectRecordDO;
/**
@@ -14,4 +19,8 @@ import fun.asgc.neutrino.proxy.server.dal.entity.ClientConnectRecordDO;
public interface ClientConnectRecordMapper extends SqlMapper {
void add(ClientConnectRecordDO clientConnectRecordDO);
@ResultType(ClientConnectRecordListRes.class)
@Select("select * from client_connect_record order by id desc")
void page(Page page, ClientConnectRecordListReq req);
}
@@ -26,6 +26,8 @@ import fun.asgc.neutrino.core.aop.Intercept;
import fun.asgc.neutrino.core.db.annotation.Delete;
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
import java.util.Date;
/**
* @author: aoshiguchen
* @date: 2022/9/17
@@ -35,21 +37,21 @@ import fun.asgc.neutrino.core.db.mapper.SqlMapper;
public interface DataCleanMapper extends SqlMapper {
@Delete("delete from `job_log` where create_time < ?")
void cleanJobLog(long date);
void cleanJobLog(Date date);
@Delete("delete from `user_login_record` where create_time < ?")
void cleanUserLoginRecord(long date);
void cleanUserLoginRecord(Date date);
@Delete("delete from `client_connect_record` where create_time < ?")
void cleanClientConnectRecord(long date);
void cleanClientConnectRecord(Date date);
@Delete("delete from `flow_report_minute` where create_time < ?")
void cleanFlowMinuteReport(long date);
void cleanFlowMinuteReport(Date date);
@Delete("delete from `flow_report_hour` where create_time < ?")
void cleanFlowHourReport(long date);
void cleanFlowHourReport(Date date);
@Delete("delete from `flow_report_day` where create_time < ?")
void cleanFlowDayReport(long date);
void cleanFlowDayReport(Date date);
}
@@ -77,7 +77,7 @@ public interface LicenseMapper extends SqlMapper {
@Update("update `license` set is_online = :isOnline, update_time = :updateTime")
void updateOnlineStatus(@Param("isOnline") Integer isOnline, @Param("updateTime") Date updateTime);
@Update("update `license` set key = :key,update_time = :updateTime where id = :id")
@Update("update `license` set `key` = :key,update_time = :updateTime where id = :id")
void reset(@Param("id") Integer id, @Param("key") String key, @Param("updateTime") Date updateTime);
@Delete("delete from `license` where id = ?")
@@ -101,6 +101,6 @@ public interface LicenseMapper extends SqlMapper {
@Select("select * from `license` where user_id = :userId and name =:name and id not in (:excludeIds) limit 0,1")
LicenseDO checkRepeat(@Param("userId") Integer userId, @Param("name") String name, @Param("excludeIds") Set<Integer> excludeIds);
@Select("select * from `license` where key = ?")
@Select("select * from `license` where `key` = ?")
LicenseDO findByKey(String licenseKey);
}
@@ -0,0 +1,22 @@
package fun.asgc.neutrino.proxy.server.dal;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Param;
import fun.asgc.neutrino.core.aop.Intercept;
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
import fun.asgc.neutrino.core.db.page.Page;
import fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes;
import java.util.Date;
/**
* @author: aoshiguchen
* @date: 2023/1/18
*/
@Intercept(ignoreGlobal = true)
@Component
public interface UserReportMapper extends SqlMapper {
void userFlowReportPage(Page<UserFlowReportRes> page, @Param("todayBegin") Date todayBegin, @Param("todayEnd") Date todayEnd);
}
@@ -56,7 +56,7 @@ public interface UserTokenMapper extends SqlMapper {
* @return
*/
// @Select("select * from user_token where token = ? and expiration_time > ?")
UserTokenDO findByAvailableToken(String token, Long time);
UserTokenDO findByAvailableToken(String token, Date date);
/**
* 根据token删除记录
@@ -84,37 +84,37 @@ public class DataCleanJob implements IJobHandler {
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getJobLogKeepDays());
log.info("清理调度管理日志 date:{}", sdf.format(date));
dataCleanMapper.cleanJobLog(date.getTime());
dataCleanMapper.cleanJobLog(date);
}
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getUserLoginRecordKeepDays());
log.info("清理用户登录日志 date:{}", sdf.format(date));
dataCleanMapper.cleanUserLoginRecord(date.getTime());
dataCleanMapper.cleanUserLoginRecord(date);
}
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getClientConnectRecordKeepDays());
log.info("清理客户端连接日志 date:{}", sdf.format(date));
dataCleanMapper.cleanClientConnectRecord(date.getTime());
dataCleanMapper.cleanClientConnectRecord(date);
}
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowMinuteReportKeepDays());
log.info("清理流通统计分钟报表日志 date:{}", sdf.format(date));
dataCleanMapper.cleanFlowMinuteReport(date.getTime());
dataCleanMapper.cleanFlowMinuteReport(date);
}
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowHourReportKeepDays());
log.info("清理流通统计小时报表日志 date:{}", sdf.format(date));
dataCleanMapper.cleanFlowHourReport(date.getTime());
dataCleanMapper.cleanFlowHourReport(date);
}
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowDayReportKeepDays());
log.info("清理流通统计日报表日志 date:{}", sdf.format(date));
dataCleanMapper.cleanFlowDayReport(date.getTime());
dataCleanMapper.cleanFlowDayReport(date);
}
}
@@ -63,11 +63,11 @@ public class FlowReportForMonthJob implements IJobHandler {
Date now = new Date();
String dateStr = DateUtil.format(DateUtil.addDate(now, Calendar.MONTH, -1), "yyyy-MM");
Date date = DateUtil.parse(dateStr, "yyyy-MM");
Date startDayDate = DateUtil.getDayBegin(date);
Date endEndDate = DateUtil.getDayEnd(date);
Date startDayDate = DateUtil.getMonthBegin(date);
Date endEndDate = DateUtil.getMonthEnd(date);
// 删除原来的记录
flowReportDayMapper.deleteByDateStr(dateStr);
flowReportMonthMapper.deleteByDateStr(dateStr);
// 查询上个月的天级别统计数据
List<FlowReportDayDO> flowReportDayList = flowReportDayMapper.findListByDateRange(startDayDate, endEndDate);
@@ -24,10 +24,26 @@ package fun.asgc.neutrino.proxy.server.service;
import fun.asgc.neutrino.core.annotation.Autowired;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.NonIntercept;
import fun.asgc.neutrino.core.db.page.Page;
import fun.asgc.neutrino.core.db.page.PageQuery;
import fun.asgc.neutrino.core.util.CollectionUtil;
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
import fun.asgc.neutrino.proxy.server.controller.req.ClientConnectRecordListReq;
import fun.asgc.neutrino.proxy.server.controller.res.ClientConnectRecordListRes;
import fun.asgc.neutrino.proxy.server.dal.ClientConnectRecordMapper;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.ClientConnectRecordDO;
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author: aoshiguchen
* @date: 2022/11/23
@@ -38,8 +54,51 @@ import lombok.extern.slf4j.Slf4j;
public class ClientConnectRecordService {
@Autowired
private ClientConnectRecordMapper clientConnectRecordMapper;
@Autowired
private LicenseMapper licenseMapper;
@Autowired
private UserMapper userMapper;
public void add(ClientConnectRecordDO clientConnectRecordDO) {
clientConnectRecordMapper.add(clientConnectRecordDO);
}
public Page<ClientConnectRecordListRes> page(PageQuery pageQuery, ClientConnectRecordListReq req) {
Page<ClientConnectRecordListRes> page = Page.create(pageQuery);
clientConnectRecordMapper.page(page, req);
if (CollectionUtil.isEmpty(page.getRecords())) {
return page;
}
Set<Integer> licenseIds = page.getRecords().stream().map(ClientConnectRecordListRes::getLicenseId).collect(Collectors.toSet());
if (CollectionUtil.isEmpty(licenseIds)) {
return page;
}
List<LicenseDO> licenseList = licenseMapper.findByIds(licenseIds);
if (CollectionUtil.isEmpty(licenseList)) {
return page;
}
Set<Integer> userIds = licenseList.stream().map(LicenseDO::getUserId).collect(Collectors.toSet());
List<UserDO> userList = userMapper.findByIds(userIds);
Map<Integer, LicenseDO> licenseMap = licenseList.stream().collect(Collectors.toMap(LicenseDO::getId, Function.identity()));
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
boolean isAdmin = SystemContextHolder.isAdmin();
page.getRecords().forEach(item -> {
LicenseDO license = licenseMap.get(item.getLicenseId());
if (null == license) {
return;
}
item.setLicenseName(license.getName());
item.setUserId(license.getUserId());
UserDO user = userMap.get(license.getUserId());
if (null == user) {
return;
}
item.setUserName(user.getName());
if (!isAdmin) {
// msg可能带有license等敏感信息,若登录者为游客,则不展示
item.setMsg("******");
}
});
return page;
}
}
@@ -0,0 +1,69 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* 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.
*/
package fun.asgc.neutrino.proxy.server.service;
import fun.asgc.neutrino.core.annotation.Autowired;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.NonIntercept;
import fun.asgc.neutrino.core.db.page.Page;
import fun.asgc.neutrino.core.db.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.LicenseFlowReportRes;
import fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes;
import fun.asgc.neutrino.proxy.server.dal.UserReportMapper;
import lombok.extern.slf4j.Slf4j;
/**
* @author: aoshiguchen
* @date: 2022/12/23
*/
@Slf4j
@NonIntercept
@Component
public class ReportService {
@Autowired
private UserReportMapper userReportMapper;
/**
* 用户流量报表分页
* @param pageQuery
* @param req
* @return
*/
public Page<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
// TODO
return null;
}
/**
* license流量报表分页
* @param pageQuery
* @param req
* @return
*/
public Page<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
// TODO
return null;
}
}
@@ -115,7 +115,7 @@ public class UserService {
public UserDO findByToken(String token) {
Date now = new Date();
UserTokenDO userTokenDO = userTokenMapper.findByAvailableToken(token, now.getTime());
UserTokenDO userTokenDO = userTokenMapper.findByAvailableToken(token, now);
if (null == userTokenDO) {
return null;
}
@@ -27,6 +27,12 @@ neutrino:
key-manager-password: 123456
jks-path: classpath:/test.jks
data:
sqlite:
db:
type: sqlite
url: jdbc:sqlite:data.db
driver-class: org.sqlite.JDBC
# type: mysql
# url: jdbc:mysql://103.163.47.16:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
# driver-class: com.mysql.jdbc.Driver
# username: "******"
# password: "******"
@@ -0,0 +1,14 @@
<mapper namespace = "fun.asgc.neutrino.proxy.server.dal.UserReportMapper">
<select id="userFlowReportPage" resultType="fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes">
SELECT
u.id AS userId,
u.NAME AS userName,
IFNULL( SUM( frm.write_bytes ), 0 ) AS historyWriteBytes,
IFNULL( SUM( frm.read_bytes ), 0 ) AS historyReadBytes
FROM `user` u
LEFT JOIN flow_report_month frm ON u.id = frm.user_id
GROUP BY u.id
</select>
</mapper>
@@ -0,0 +1,192 @@
##########################################################
#
CREATE TABLE IF NOT EXISTS `user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(50) NOT NULL COMMENT '用户名',
`login_name` varchar(50) NOT NULL COMMENT '登录名',
`login_password` varchar(255) NOT NULL COMMENT '登录密码',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_user_login_name` (`login_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
#token表
CREATE TABLE IF NOT EXISTS `user_token` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`token` varchar(50) NOT NULL COMMENT 'token',
`user_id` int NOT NULL COMMENT '用户ID',
`expiration_time` datetime(3) NOT NULL COMMENT '过期时间',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_user_token_user_id` (`user_id`),
KEY `I_user_token_token` (`token`),
KEY `I_user_token_expiration_time` (`expiration_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
#
CREATE TABLE IF NOT EXISTS `port_pool` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`port` int NOT NULL COMMENT '端口',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_port_pool_port` (`port`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
##########################################################
#license表
CREATE TABLE IF NOT EXISTS `license` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(50) NOT NULL COMMENT 'license名称',
`key` varchar(100) NOT NULL COMMENT 'license key',
`user_id` int NOT NULL COMMENT '用户ID',
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `I_license_key` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
#
CREATE TABLE IF NOT EXISTS `port_mapping` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`license_id` int NOT NULL COMMENT 'licenseID',
`server_port` int NOT NULL COMMENT '服务端端口',
`client_ip` varchar(20) NOT NULL COMMENT '客户端IP',
`client_port` int NOT NULL COMMENT '客户端端口',
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
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` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int NOT NULL COMMENT '用户ID',
`ip` varchar(50) NOT NULL COMMENT 'IP',
`token` varchar(100) NOT NULL COMMENT 'token',
`type` int NOT NULL COMMENT '类型(1、登录 2、登出)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
#
CREATE TABLE IF NOT EXISTS `client_connect_record` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'IP',
`license_id` int NOT NULL COMMENT 'licenseId',
`type` int NOT NULL COMMENT '类型(1、连接 2、断开连接)',
`msg` varchar(512) DEFAULT NULL COMMENT '消息',
`code` int NOT NULL COMMENT '结果 1、成功 2、失败)',
`err` text DEFAULT NULL COMMENT '异常信息',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
##########################################################
#
CREATE TABLE IF NOT EXISTS `job_info` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`desc` varchar(255) NOT NULL COMMENT '描述',
`handler` varchar(255) NOT NULL COMMENT '处理器',
`cron` varchar(128) NOT NULL COMMENT 'cron',
`param` varchar(512) DEFAULT NULL COMMENT '参数',
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮箱',
`alarm_ding` varchar(255) DEFAULT NULL COMMENT '报警钉钉配置',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_job_info_handler` (`handler`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
#
CREATE TABLE IF NOT EXISTS `job_log` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`job_id` int NOT NULL COMMENT 'JobId',
`handler` varchar(255) NOT NULL COMMENT '处理器',
`param` varchar(512) DEFAULT NULL COMMENT '参数',
`code` int NOT NULL COMMENT '结果(1、成功 2、失败)',
`msg` text COMMENT '消息',
`alarm_status` int NOT NULL COMMENT '报警状态(1、未报警 2、已报警)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `I_job_log_create_time` (`create_time`) USING BTREE,
KEY `I_job_log_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
##########################################################
#-(24)
CREATE TABLE IF NOT EXISTS `flow_report_minute` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int NOT NULL COMMENT '用户ID',
`license_id` int NOT NULL COMMENT 'licenseId',
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH:mm',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_minute_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_minute_date` (`date`) USING BTREE,
KEY `I_flow_report_minute_user_id` (`user_id`),
KEY `I_flow_report_minute_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
#-(60)
CREATE TABLE IF NOT EXISTS `flow_report_hour` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int NOT NULL COMMENT '用户ID',
`license_id` int NOT NULL COMMENT 'licenseId',
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM-dd HH',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_hour_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_hour_date` (`date`) USING BTREE,
KEY `I_flow_report_hour_user_id` (`user_id`),
KEY `I_flow_report_hour_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
#-(1)
CREATE TABLE IF NOT EXISTS `flow_report_day` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int NOT NULL COMMENT '用户ID',
`license_id` int NOT NULL COMMENT 'licenseId',
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM-dd',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_day_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_day_date` (`date`) USING BTREE,
KEY `I_flow_report_day_user_id` (`user_id`),
KEY `I_flow_report_day_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
#-()
CREATE TABLE IF NOT EXISTS `flow_report_month` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int NOT NULL COMMENT '用户ID',
`license_id` int NOT NULL COMMENT 'licenseId',
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_month_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_month_date` (`date`) USING BTREE,
KEY `I_flow_report_month_user_id` (`user_id`),
KEY `I_flow_report_month_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
@@ -0,0 +1,13 @@
#job_qrtz_trigger_info
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(1, '示例Job', 'DemoJob', '0/10 * * * * ?', '{"a":101}', 1, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(2, '数据清理任务', 'DataCleanJob', '0 0 1 * * ?', '', 1, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(3, '流量统计报表-分钟', 'FlowReportForMinuteJob', '0 */1 * * * ?', '', 1, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(4, '流量统计报表-小时', 'FlowReportForHourJob', '0 0 */1 * * ?', '', 1, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(5, '流量统计报表-天', 'FlowReportForDayJob', '0 0 1 * * ?', '', 1, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(6, '流量统计报表-月', 'FlowReportForMonthJob', '0 30 1 1 * ?', '', 1, now(), now());
@@ -0,0 +1,3 @@
#license
INSERT INTO license(`id`, `name`, `key`, `user_id`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, '我的mac', 'b0a907332b474b25897c4dcb31fc7eb6', 1, 2, 1, now(), now());
@@ -0,0 +1,5 @@
#port_mapping
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, '127.0.0.1', 8080, 2, 1, now(), now());
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, '127.0.0.1', 3306, 2, 1, now(), now());
@@ -0,0 +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());
@@ -0,0 +1,5 @@
# 6613b92b77056faeb72068f184ed4c4f
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
(1, '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
(2, '游客', 'visitor', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
+12 -6
View File
@@ -1,7 +1,13 @@
# BUG
- neutrino-proxy-admin 打包后启动,token失效不会跳回登录页面
# 功能点
- 用户流量报表
- 弹框展示月度明细
- 弹框展示今日流量明细
- License流量报表
- 弹框展示月度明细
- 弹框展示今日流量明细
- 首页图表📈
# 优化
- 调度管理,增加查看按钮,解决异常情况下,列表展示堆栈异常信息不全,不方便查看的问题
- 用户列表增加修改密码入口,管理员可以修改指定用户密码,无需验证原密码(仅管理员操作
- 增加当前登录用户修改自己密码的功能,需要验证原密码
# Bug
- windows环境下直接运行发布版的jar包,日志输出乱码
- 部份用户windows环境下启动客户端,扫描类个数为0个
- 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象