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

This commit is contained in:
aoshiguchen
2023-02-06 10:14:25 +08:00
25 changed files with 1207 additions and 168 deletions
@@ -21,15 +21,15 @@
*/
package fun.asgc.neutrino.core.db.template;
import fun.asgc.neutrino.core.db.annotation.Id;
import fun.asgc.neutrino.core.util.CollectionUtil;
import fun.asgc.neutrino.core.util.ReflectUtil;
import fun.asgc.neutrino.core.util.TypeUtil;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
*
@@ -38,6 +38,8 @@ import java.util.Map;
*/
public class JdbcOperations {
private static final JdbcOperations instance = new JdbcOperations();
private static final Map<Class<?>, Field> generateIdFieldMap = new ConcurrentHashMap<>();
private JdbcOperations() {
@@ -85,6 +87,45 @@ public class JdbcOperations {
});
}
/**
* 执行更新操作
* 临时兼容返设主键问题
* @param conn
* @param sql
* @param params
* @return
*/
public int executeUpdateByModel(final Connection conn , final String sql, final Object model, final Object[] params) throws SQLException {
return this.execute(new PreparedStatementJdbcCallback<Integer>(){
@Override
public Integer execute(PreparedStatement ps) throws SQLException {
Integer res = ps.executeUpdate();
if (null != model) {
ResultSet resultSet = ps.getGeneratedKeys();
if (resultSet.next()) {
Field field = getGenerateIdField(model.getClass());
if (null != field) {
ReflectUtil.setFieldValue(field, model, resultSet.getInt(1));
}
}
}
return res;
}
@Override
public Object[] getParams() {
return params;
}
@Override
public String getSql() {
return sql;
}
@Override
public Connection getConnection(){
return conn;
}
});
}
/**
* 执行单条查询操作
* @param conn
@@ -225,4 +266,28 @@ public class JdbcOperations {
});
}
/**
* 获取自动生成ID字段
* @param clazz
* @return
*/
private static Field getGenerateIdField(Class<?> clazz) {
if (null == clazz) {
return null;
}
if (generateIdFieldMap.containsKey(clazz)) {
return generateIdFieldMap.get(clazz);
}
Set<Field> fields = ReflectUtil.getDeclaredFields(clazz);
if (CollectionUtil.isEmpty(fields)) {
return null;
}
Field field = fields.stream().filter(f -> f.isAnnotationPresent(Id.class)).findFirst().orElse(null);
if (null != field) {
return field;
}
field = fields.stream().filter(f -> f.getName().equals("id")).findFirst().orElse(null);
return field;
}
}
@@ -21,16 +21,12 @@
*/
package fun.asgc.neutrino.core.db.template;
import fun.asgc.neutrino.core.util.ArrayUtil;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
*
@@ -74,6 +70,31 @@ public class JdbcTemplate {
return res;
}
/**
* TODO 临时用来兼容insert之后需要返设主键的问题
* @param sql
* @param params
* @return
* @throws SQLException
*/
public int updateByModel(String sql, Object model, Object ...params) throws SQLException {
int res = -1;
Connection conn = null;
try {
conn = dataSourceHolder.getConnection();
res = jdbcOperations.executeUpdateByModel(conn,sql, model, params);
} finally {
try {
dataSourceHolder.tryClose(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
return res;
}
public int update(SqlAndParams sqlAndParams) throws SQLException {
return update(sqlAndParams.getSql(), sqlAndParams.getParamArray());
}
@@ -83,7 +104,8 @@ public class JdbcTemplate {
}
public int updateByModel(String sql, Object model) throws SQLException {
return update(new SqlAndParams(sql, model));
SqlAndParams sqlAndParams = new SqlAndParams(sql, model);
return updateByModel(sqlAndParams.getSql(), model, sqlAndParams.getParamArray());
}
public <T> T query(Class<T> clazz, String sql, Object ...params) throws SQLException {
@@ -0,0 +1,118 @@
<template>
<div :class="className" :id="dailyTrafficChart.isHistory ? 'history-traffic-div' : 'daily-traffic-div'" :style="{height:height,width:width}"></div>
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '160px'
},
dailyTrafficChart: {
type: Object,
default: () => {
return {
upload: 90, // 上行
download: 100, // 下行
isHistory: false
}
}
}
},
data() {
return {
chartDom: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chartDom.dispose()
this.chartDom = null
},
methods: {
initChart() {
const isHistory = this.dailyTrafficChart.isHistory
this.chartDom = document.getElementById(isHistory ? 'history-traffic-div' : 'daily-traffic-div')
this.myChart = echarts.init(this.chartDom)
const option = {
title: {
text: isHistory ? '历史流量' : '今日流量',
left: 'center',
bottom: '0',
textStyle: {
fontSize: 12,
fontWeight: 800,
color: '#6c7a89'
}
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: isHistory ? '历史流量' : '今日流量',
type: 'pie',
radius: '68%',
// 隐藏指示线
labelLine: {
normal: {
show: false
}
},
label: {
normal: {
show: true,
position: 'inner',
fontSize: 10,
color: '#fff',
formatter: (data) => {
return `${data.name}${data.value > 1000 ? '\n\n' : ''}${data.value}`
}
}
},
data: [
{
value: this.dailyTrafficChart.upload,
name: '上行'
},
{
value: this.dailyTrafficChart.download,
name: '下行'
}
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
option && this.myChart.setOption(option)
}
}
}
</script>
@@ -0,0 +1,111 @@
<template>
<div :class="className" id="license-div" :style="{height:height,width:width}"></div>
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '160px'
},
licenseChart: {
type: Object,
default: () => {
return {
onLine: 90, // 进度条最大值
total: 100 // 当前进度
}
}
}
},
data() {
return {
chartDom: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chartDom.dispose()
this.chartDom = null
},
methods: {
initChart() {
this.chartDom = document.getElementById('license-div')
this.myChart = echarts.init(this.chartDom)
const option = {
title: {
text: this.licenseChart.onLine,
subtext: 'License在线数',
left: 'center',
top: '32%',
textStyle: {
fontSize: 22,
fontWeight: 800,
color: '#c23531',
align: 'center'
},
subtextStyle: {
fontSize: 10,
fontWeight: 800,
color: '#6c7a89'
}
// bottom:'0'
},
tooltip: {
trigger: 'item'
},
series: [
{
// 第一张圆环
name: 'License',
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '50%'],
// 隐藏指示线
labelLine: {
normal: {
show: false
}
},
// 隐藏圆环上文字
label: {
normal: {
show: false
}
},
data: [
// value当前进度 + 颜色
{
name: '在线数',
value: this.licenseChart.onLine
},
{
name: '离线数',
value: this.licenseChart.total - this.licenseChart.onLine
}
]
}
]
}
option && this.myChart.setOption(option)
}
}
}
</script>
@@ -0,0 +1,111 @@
<template>
<div :class="className" id="port-mapping-div" :style="{height:height,width:width}"></div>
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '160px'
},
portMappingChart: {
type: Object,
default: () => {
return {
onLine: 90, // 进度条最大值
total: 100 // 当前进度
}
}
}
},
data() {
return {
chartDom: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chartDom.dispose()
this.chartDom = null
},
methods: {
initChart() {
this.chartDom = document.getElementById('port-mapping-div')
this.myChart = echarts.init(this.chartDom)
const option = {
title: {
text: this.portMappingChart.onLine,
subtext: '端口映射在线数',
left: 'center',
top: '32%',
textStyle: {
fontSize: 22,
fontWeight: 800,
color: '#c23531',
align: 'center'
},
subtextStyle: {
fontSize: 10,
fontWeight: 800,
color: '#6c7a89'
}
// bottom:'0'
},
tooltip: {
trigger: 'item'
},
series: [
{
// 第一张圆环
name: '端口映射',
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '50%'],
// 隐藏指示线
labelLine: {
normal: {
show: false
}
},
// 隐藏圆环上文字
label: {
normal: {
show: false
}
},
data: [
// value当前进度 + 颜色
{
name: '在线数',
value: this.portMappingChart.onLine
},
{
name: '离线数',
value: this.portMappingChart.total - this.portMappingChart.onLine
}
]
}
]
}
option && this.myChart.setOption(option)
}
}
}
</script>
@@ -0,0 +1,39 @@
<template>
<div id="port-mapping-div" :style="{height:height+'px',width:width}">
<el-table
:data="tableChart"
style="width: 100%"
:default-sort = "{prop: 'date', order: 'descending'}"
:height="height"
>
<el-table-column prop="date" label="日期" sortable />
<el-table-column prop="name" label="姓名" sortable />
<el-table-column prop="address" label="地址" sortable show-overflow-tooltip/>
</el-table>
</div>
</template>
<script>
export default {
props: {
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '340'
},
tableChart: {
type: Array,
default: []
}
},
data() {
return {
}
}
}
</script>
@@ -0,0 +1,97 @@
<template>
<div :class="className" id="traffic-sum-div" :style="{height:height,width:width}"></div>
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '380px'
},
licenseChart: {
type: Object,
default: () => {
return {
onLine: 90, // 进度条最大值
total: 100 // 当前进度
}
}
}
},
data() {
return {
chartDom: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chartDom.dispose()
this.chartDom = null
},
methods: {
initChart() {
this.chartDom = document.getElementById('traffic-sum-div')
this.myChart = echarts.init(this.chartDom)
const option = {
title: {
text: '今日流量折线图',
subtext: '当日0-24时'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['上行', '下行'],
left: 'right'
},
grid: {
left: '2%',
right: '2%',
bottom: '2%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1:00', '2:00', '3:00', '4:00', '5:00', '6:00', '7:00']
},
yAxis: {
type: 'value'
},
series: [
{
name: '上行',
type: 'line',
stack: 'Total',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '下行',
type: 'line',
stack: 'Total',
data: [220, 182, 191, 234, 290, 330, 310]
}
]
}
option && this.myChart.setOption(option)
}
}
}
</script>
@@ -0,0 +1,110 @@
<template>
<div class="dashboard-editor-container">
<github-corner></github-corner>
<panel-group @handleSetLineChartData="handleSetLineChartData"></panel-group>
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<line-chart :chart-data="lineChartData"></line-chart>
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<raddar-chart></raddar-chart>
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<pie-chart></pie-chart>
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<bar-chart></bar-chart>
</div>
</el-col>
</el-row>
<el-row :gutter="8">
<el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="padding-right:8px;margin-bottom:30px;">
<transaction-table></transaction-table>
</el-col>
<el-col :xs="{span: 12}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 5}">
<todo-list></todo-list>
</el-col>
<el-col :xs="{span: 12}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 5}">
<box-card></box-card>
</el-col>
</el-row>
</div>
</template>
<script>
import GithubCorner from '@/components/GithubCorner'
import PanelGroup from './components/PanelGroup'
import LineChart from './components/LineChart'
import RaddarChart from './components/RaddarChart'
import PieChart from './components/PieChart'
import BarChart from './components/BarChart'
import TransactionTable from './components/TransactionTable'
import TodoList from './components/TodoList'
import BoxCard from './components/BoxCard'
const lineChartData = {
newVisitis: {
expectedData: [100, 120, 161, 134, 105, 160, 165],
actualData: [120, 82, 91, 154, 162, 140, 145]
},
messages: {
expectedData: [200, 192, 120, 144, 160, 130, 140],
actualData: [180, 160, 151, 106, 145, 150, 130]
},
purchases: {
expectedData: [80, 100, 121, 104, 105, 90, 100],
actualData: [120, 90, 100, 138, 142, 130, 130]
},
shoppings: {
expectedData: [130, 140, 141, 142, 145, 150, 160],
actualData: [120, 82, 91, 154, 162, 140, 130]
}
}
export default {
name: 'dashboard-admin',
components: {
GithubCorner,
PanelGroup,
LineChart,
RaddarChart,
PieChart,
BarChart,
TransactionTable,
TodoList,
BoxCard
},
data() {
return {
lineChartData: lineChartData.newVisitis
}
},
methods: {
handleSetLineChartData(type) {
this.lineChartData = lineChartData[type]
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-editor-container {
padding: 32px;
background-color: rgb(240, 242, 245);
.chart-wrapper {
background: #fff;
padding: 16px 16px 0;
margin-bottom: 32px;
}
}
</style>
@@ -1,110 +1,107 @@
<template>
<div class="dashboard-editor-container">
<github-corner></github-corner>
<panel-group @handleSetLineChartData="handleSetLineChartData"></panel-group>
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<line-chart :chart-data="lineChartData"></line-chart>
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<raddar-chart></raddar-chart>
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<pie-chart></pie-chart>
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<bar-chart></bar-chart>
</div>
</el-col>
</el-row>
<el-row :gutter="8">
<el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="padding-right:8px;margin-bottom:30px;">
<transaction-table></transaction-table>
<el-col :span="12">
<el-card class="box-card">
<el-row>
<el-col :span="24">
<el-row>
<el-col :span="12">
<license-chart :licenseChart="echartsData.licenseChart"/>
</el-col>
<el-col :span="12">
<port-mapping-chart :portMappingChart="echartsData.portMappingChart"/>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<daily-traffic-chart :dailyTrafficChart="echartsData.dailyTrafficChart"/>
</el-col>
<el-col :span="12">
<daily-traffic-chart :dailyTrafficChart="echartsData.historyTrafficChart"/>
</el-col>
</el-row>
</el-col>
</el-row>
</el-card>
</el-col>
<el-col :xs="{span: 12}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 5}">
<todo-list></todo-list>
<el-col :span="12">
<el-card class="box-card">
<el-col :span="24">
<table-chart :tableChart="echartsData.tableChart"/>
</el-col>
</el-card>
</el-col>
<el-col :xs="{span: 12}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 5}">
<box-card></box-card>
</el-row>
<el-row class="line-chart">
<el-col :span="24">
<el-card>
<traffic-sum-chart/>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import GithubCorner from '@/components/GithubCorner'
import PanelGroup from './components/PanelGroup'
import LineChart from './components/LineChart'
import RaddarChart from './components/RaddarChart'
import PieChart from './components/PieChart'
import BarChart from './components/BarChart'
import TransactionTable from './components/TransactionTable'
import TodoList from './components/TodoList'
import BoxCard from './components/BoxCard'
const lineChartData = {
newVisitis: {
expectedData: [100, 120, 161, 134, 105, 160, 165],
actualData: [120, 82, 91, 154, 162, 140, 145]
import LicenseChart from './components/LicenseChart'
import PortMappingChart from './components/PortMappingChart'
import DailyTrafficChart from './components/DailyTrafficChart'
import TableChart from './components/TableChart'
import TrafficSumChart from './components/TrafficSumChart'
const echartsData = {
licenseChart: {
total: 100,
onLine: 80
},
messages: {
expectedData: [200, 192, 120, 144, 160, 130, 140],
actualData: [180, 160, 151, 106, 145, 150, 130]
portMappingChart: {
total: 100,
onLine: 25
},
purchases: {
expectedData: [80, 100, 121, 104, 105, 90, 100],
actualData: [120, 90, 100, 138, 142, 130, 130]
dailyTrafficChart: {
upload: 300,
download: 680,
isHistory: false
},
shoppings: {
expectedData: [130, 140, 141, 142, 145, 150, 160],
actualData: [120, 82, 91, 154, 162, 140, 130]
}
historyTrafficChart: {
upload: 4100,
download: 9520,
isHistory: true
},
tableChart: [
{ date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' },
{ date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄' },
{ date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1519 弄' },
{ date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1516 弄' },
{ date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄' }
]
}
export default {
name: 'dashboard-admin',
components: {
GithubCorner,
PanelGroup,
LineChart,
RaddarChart,
PieChart,
BarChart,
TransactionTable,
TodoList,
BoxCard
},
components: { TrafficSumChart, TableChart, DailyTrafficChart, PortMappingChart, LicenseChart },
data() {
return {
lineChartData: lineChartData.newVisitis
echartsData: echartsData
}
},
mounted() {
},
methods: {
handleSetLineChartData(type) {
this.lineChartData = lineChartData[type]
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-editor-container {
padding: 32px;
min-height: calc(100vh - 85px);
padding: 16px;
background-color: rgb(240, 242, 245);
.chart-wrapper {
background: #fff;
padding: 16px 16px 0;
margin-bottom: 32px;
.line-chart {
margin-top: 16px;
}
}
</style>
@@ -24,6 +24,11 @@ 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
@@ -34,7 +39,11 @@ import lombok.Getter;
public enum EnableStatusEnum {
ENABLE(1, "启用"),
DISABLE(2, "禁用");
private static Map<Integer, EnableStatusEnum> CACHE = Stream.of(EnableStatusEnum.values()).collect(Collectors.toMap(EnableStatusEnum::getStatus, Function.identity()));
private Integer status;
private String desc;
public static EnableStatusEnum of(Integer status) {
return CACHE.get(status);
}
}
@@ -50,7 +50,7 @@ public enum ExceptionConstant {
LOGIN_PASSWORD_NO_CHANGE_MODIFY_FAIL(12004, "密码没有变化,修改失败"),
// 端口池管理(13000)
PORT_CANNOT_REPEAT(13000,"端口不能重复"),
PORT_NOT_EXIST(13001, "该端口在端口池中不存在,不允许映射"),
PORT_NOT_EXIST(13001, "该端口在端口池中不存在"),
// 端口映射管理(14000)
PORT_MAPPING_NOT_EXIST(14000, "端口映射记录不存在"),
PORT_CANNOT_REPEAT_MAPPING(14001, "服务端口[{}]不能重复映射"),
@@ -62,6 +62,10 @@ public interface LicenseMapper extends SqlMapper {
@Select("select * from license")
List<LicenseDO> listAll();
@ResultType(LicenseDO.class)
@Select("select * from `license` where user_id = :userId")
List<LicenseDO> listByUserId(@Param("userId") Integer userId);
/**
* 新增license
* @param license
@@ -74,6 +74,14 @@ public interface PortMappingMapper extends SqlMapper {
@Select("select * from port_mapping where license_id = ? and enable = 1")
List<PortMappingDO> findEnableListByLicenseId(Integer licenseId);
@ResultType(PortMappingDO.class)
@Select("select * from port_mapping where server_port = :serverPort")
List<PortMappingDO> findListByServerPort(@Param("serverPort") Integer serverPort);
@ResultType(PortMappingDO.class)
@Select("select * from port_mapping where license_id = ?")
List<PortMappingDO> findListByLicenseId(Integer licenseId);
@Update("update `port_mapping` set is_online = :isOnline,update_time = :updateTime where license_id = :licenseId and server_port = :serverPort")
void updateOnlineStatus(@Param("licenseId") Integer licenseId, @Param("serverPort") Integer serverPort, @Param("isOnline") Integer isOnline, @Param("updateTime") Date updateTime);
@@ -62,4 +62,7 @@ public interface PortPoolMapper extends SqlMapper {
@Select("select * from port_pool where port = ? limit 0,1")
PortPoolDO findByPort(Integer port);
@Select("select * from port_pool where id = ?")
PortPoolDO findById(Integer id);
}
@@ -23,6 +23,7 @@
package fun.asgc.neutrino.proxy.server.proxy.core;
import fun.asgc.neutrino.core.util.BeanManager;
import fun.asgc.neutrino.core.util.StringUtil;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.server.proxy.domain.VisitorChannelAttachInfo;
@@ -87,10 +88,15 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
} else {
String visitorId = newVisitorId();
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel);
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
if (StringUtil.isEmpty(lanInfo)) {
ctx.channel().close();
} else {
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, sa.getPort());
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
}
}
super.channelActive(ctx);
@@ -133,17 +139,17 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel userChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
Channel visitorChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null) {
proxyChannel.config().setOption(ChannelOption.AUTO_READ, userChannel.isWritable());
proxyChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
}
}
@@ -52,11 +52,15 @@ public class ProxyMapping {
return list;
}
for (PortMappingDO portMapping : portMappingList) {
list.add(new ProxyMapping()
.setServerPort(portMapping.getServerPort())
.setLanInfo(String.format("%s:%s", portMapping.getClientIp(), portMapping.getClientPort())));
list.add(build(portMapping));
}
return list;
}
public static ProxyMapping build(PortMappingDO portMappingDO) {
return new ProxyMapping()
.setServerPort(portMappingDO.getServerPort())
.setLanInfo(String.format("%s:%s", portMappingDO.getClientIp(), portMappingDO.getClientPort()));
}
}
@@ -34,6 +34,7 @@ import lombok.experimental.Accessors;
public class VisitorChannelAttachInfo {
private String visitorId;
private String lanInfo;
private Integer serverPort;
/**
* licenseId
*/
@@ -27,36 +27,24 @@ import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Match;
import fun.asgc.neutrino.core.annotation.NonIntercept;
import fun.asgc.neutrino.core.util.ChannelUtil;
import fun.asgc.neutrino.core.util.CollectionUtil;
import fun.asgc.neutrino.core.util.StringUtil;
import fun.asgc.neutrino.proxy.core.*;
import fun.asgc.neutrino.proxy.server.base.proxy.ProxyConfig;
import fun.asgc.neutrino.proxy.server.constant.ClientConnectTypeEnum;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.SuccessCodeEnum;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
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.PortMappingDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import fun.asgc.neutrino.proxy.server.proxy.core.BytesMetricsHandler;
import fun.asgc.neutrino.proxy.server.proxy.core.VisitorChannelHandler;
import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo;
import fun.asgc.neutrino.proxy.server.proxy.domain.ProxyMapping;
import fun.asgc.neutrino.proxy.server.service.*;
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.net.BindException;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
*
@@ -68,10 +56,6 @@ import java.util.stream.Collectors;
@Match(type = Constants.ProxyDataTypeName.AUTH)
@Component
public class ProxyMessageAuthHandler implements ProxyMessageHandler {
@Autowired("serverBossGroup")
private NioEventLoopGroup serverBossGroup;
@Autowired("serverWorkerGroup")
private NioEventLoopGroup serverWorkerGroup;
@Autowired
private ProxyConfig proxyConfig;
@Autowired
@@ -86,6 +70,10 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
private FlowReportService flowReportService;
@Autowired
private ClientConnectRecordService clientConnectRecordService;
@Autowired
private LicenseMapper licenseMapper;
@Autowired
private VisitorChannelService visitorChannelService;
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
@@ -167,44 +155,14 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
.setCode(SuccessCodeEnum.SUCCESS.getCode())
.setCreateTime(now));
List<PortMappingDO> portMappingList = portMappingService.findEnableListByLicenseId(licenseDO.getId());
// 没有端口映射仍然保持连接
if (!CollectionUtil.isEmpty(portMappingList)) {
ProxyUtil.initProxyInfo(licenseDO.getId(), ProxyMapping.buildList(portMappingList));
ProxyUtil.addCmdChannel(licenseDO.getId(), ctx.channel(), portMappingList.stream().map(PortMappingDO::getServerPort).collect(Collectors.toSet()));
startUserPortServer(ProxyUtil.getAttachInfo(ctx.channel()), portMappingList);
}
// 更新license在线状态
licenseMapper.updateOnlineStatus(licenseDO.getId(), OnlineStatusEnum.ONLINE.getStatus(), now);
// 初始化VisitorChannel
visitorChannelService.initVisitorChannel(licenseDO.getId(), ctx.channel());
}
@Override
public String name() {
return ProxyDataTypeEnum.AUTH.getDesc();
}
private void startUserPortServer(CmdChannelAttachInfo cmdChannelAttachInfo, List<PortMappingDO> portMappingList) {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(serverBossGroup, serverWorkerGroup)
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new VisitorChannelHandler());
}
});
for (PortMappingDO portMapping : portMappingList) {
try {
proxyMutualService.bindServerPort(cmdChannelAttachInfo, portMapping.getServerPort());
bootstrap.bind(portMapping.getServerPort()).get();
log.info("绑定用户端口: {}", portMapping.getServerPort());
} catch (Exception ex) {
// BindException表示该端口已经绑定过
if (!(ex.getCause() instanceof BindException)) {
throw new RuntimeException(ex);
}
}
}
}
}
@@ -58,6 +58,8 @@ public class LicenseService {
private LicenseMapper licenseMapper;
@Autowired
private UserMapper userMapper;
@Autowired
private VisitorChannelService visitorChannelService;
public Page<LicenseListRes> page(PageQuery pageQuery, LicenseListReq req) {
Page<LicenseListRes> page = Page.create(pageQuery);
@@ -159,7 +161,8 @@ public class LicenseService {
*/
public LicenseUpdateEnableStatusRes updateEnableStatus(LicenseUpdateEnableStatusReq req) {
licenseMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByLicenseId(req.getId(), req.getEnable());
return new LicenseUpdateEnableStatusRes();
}
@@ -169,6 +172,8 @@ public class LicenseService {
*/
public void delete(Integer id) {
licenseMapper.delete(id);
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByLicenseId(id, EnableStatusEnum.DISABLE.getStatus());
}
/**
@@ -68,6 +68,8 @@ public class PortMappingService {
private UserMapper userMapper;
@Autowired
private PortPoolMapper portPoolMapper;
@Autowired
private VisitorChannelService visitorChannelService;
public Page<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
Page<PortMappingListRes> page = Page.create(pageQuery);
@@ -126,6 +128,8 @@ public class PortMappingService {
portMappingDO.setCreateTime(now);
portMappingDO.setUpdateTime(now);
portMappingMapper.add(portMappingDO);
// 更新VisitorChannel
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
return new PortMappingCreateRes();
}
@@ -140,6 +144,10 @@ public class PortMappingService {
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
// 查询原端口映射
PortMappingDO oldPortMappingDO = portMappingMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(oldPortMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
PortMappingDO portMappingDO = new PortMappingDO();
portMappingDO.setId(req.getId());
portMappingDO.setLicenseId(req.getLicenseId());
@@ -147,7 +155,10 @@ public class PortMappingService {
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
portMappingDO.setUpdateTime(new Date());
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
portMappingMapper.update(portMappingDO);
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
return new PortMappingUpdateRes();
}
@@ -193,6 +204,14 @@ public class PortMappingService {
portMappingMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
// 更新VisitorChannel
portMappingDO.setEnable(req.getEnable());
if (EnableStatusEnum.ENABLE == EnableStatusEnum.of(req.getEnable())) {
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
} else {
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
}
return new PortMappingUpdateEnableStatusRes();
}
@@ -207,6 +226,9 @@ public class PortMappingService {
}
portMappingMapper.delete(id);
// 更新VisitorChannel
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
}
/**
@@ -52,6 +52,8 @@ public class PortPoolService {
@Autowired
private PortPoolMapper portPoolMapper;
@Autowired
private VisitorChannelService visitorChannelService;
public Page<PortPoolListRes> page(PageQuery pageQuery, PortPoolListReq req) {
Page<PortPoolListRes> page = Page.create(pageQuery);
@@ -65,7 +67,7 @@ public class PortPoolService {
public PortPoolCreateRes create(PortPoolCreateReq req) {
PortPoolDO oldPortPoolDO = portPoolMapper.findByPort(req.getPort());
ParamCheckUtil.checkNotNull(oldPortPoolDO, ExceptionConstant.PORT_CANNOT_REPEAT);
ParamCheckUtil.checkMustNull(oldPortPoolDO, ExceptionConstant.PORT_CANNOT_REPEAT);
Date now = new Date();
@@ -75,19 +77,31 @@ public class PortPoolService {
.setCreateTime(now)
.setUpdateTime(now)
);
// 更新visitorChannel
visitorChannelService.updateVisitorChannelByPortPool(req.getPort(), EnableStatusEnum.ENABLE.getStatus());
return new PortPoolCreateRes();
}
public PortPoolUpdateEnableStatusRes updateEnableStatus(PortPoolUpdateEnableStatusReq req) {
PortPoolDO portPoolDO = portPoolMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
portPoolMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
// 更新visitorChannel
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), req.getEnable());
return new PortPoolUpdateEnableStatusRes();
}
public void delete(Integer id) {
PortPoolDO portPoolDO = portPoolMapper.findById(id);
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
portPoolMapper.delete(id);
// 更新visitorChannel
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), EnableStatusEnum.DISABLE.getStatus());
}
}
@@ -62,6 +62,8 @@ public class UserService {
private UserTokenMapper userTokenMapper;
@Autowired
private UserLoginRecordMapper userLoginRecordMapper;
@Autowired
private VisitorChannelService visitorChannelService;
public LoginRes login(LoginReq req) {
UserDO userDO = userMapper.findByLoginName(req.getLoginName());
@@ -162,7 +164,8 @@ public class UserService {
public UserUpdateEnableStatusRes updateEnableStatus(UserUpdateEnableStatusReq req) {
userMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByUserId(req.getId(), req.getEnable());
return new UserUpdateEnableStatusRes();
}
@@ -208,5 +211,7 @@ public class UserService {
public void delete(Integer id) {
userMapper.delete(id);
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByUserId(id, EnableStatusEnum.DISABLE.getStatus());
}
}
@@ -0,0 +1,266 @@
/**
* 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 com.google.common.collect.Lists;
import com.google.common.collect.Sets;
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.util.CollectionUtil;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.PortMappingMapper;
import fun.asgc.neutrino.proxy.server.dal.PortPoolMapper;
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
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.proxy.core.BytesMetricsHandler;
import fun.asgc.neutrino.proxy.server.proxy.core.VisitorChannelHandler;
import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo;
import fun.asgc.neutrino.proxy.server.proxy.domain.ProxyMapping;
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.net.BindException;
import java.util.List;
import java.util.stream.Collectors;
/**
* 访问者通道服务
* @author: aoshiguchen
* @date: 2023/2/5
*/
@Slf4j
@NonIntercept
@Component
public class VisitorChannelService {
@Autowired("serverBossGroup")
private NioEventLoopGroup serverBossGroup;
@Autowired("serverWorkerGroup")
private NioEventLoopGroup serverWorkerGroup;
@Autowired
private ProxyMutualService proxyMutualService;
@Autowired
private UserMapper userMapper;
@Autowired
private LicenseMapper licenseMapper;
@Autowired
private PortMappingMapper portMappingMapper;
@Autowired
private PortPoolMapper portPoolMapper;
/**
* 初始化
* @param licenseId
*/
public void initVisitorChannel(Integer licenseId, Channel cmdChannel) {
List<PortMappingDO> portMappingList = portMappingMapper.findEnableListByLicenseId(licenseId);
// 没有端口映射仍然保持连接
ProxyUtil.initProxyInfo(licenseId, ProxyMapping.buildList(portMappingList));
ProxyUtil.addCmdChannel(licenseId, cmdChannel, portMappingList.stream().map(PortMappingDO::getServerPort).collect(Collectors.toSet()));
startUserPortServer(ProxyUtil.getAttachInfo(cmdChannel), portMappingList);
}
/**
* 更新
* 触发时机:删除端口池、禁用端口池、启用端口池
* @param serverPort
* @param enable
*/
public void updateVisitorChannelByPortPool(Integer serverPort, Integer enable) {
if (null == serverPort) {
return;
}
List<PortMappingDO> portMappingDOList = portMappingMapper.findListByServerPort(serverPort);
if (CollectionUtil.isEmpty(portMappingDOList)) {
return;
}
EnableStatusEnum enableStatusEnum = EnableStatusEnum.of(enable);
for (PortMappingDO portMappingDO : portMappingDOList) {
if (EnableStatusEnum.DISABLE == enableStatusEnum) {
removeVisitorChannelByPortMapping(portMappingDO);
} else if (EnableStatusEnum.ENABLE == EnableStatusEnum.of(portMappingDO.getEnable())) {
addVisitorChannelByPortMapping(portMappingDO);
}
}
}
/**
* 更新
* 触发时机:删除用户、禁用用户、启用用户 (新增、修改用户不涉及VisitorChannel的变更)
* @param userId
*/
public void updateVisitorChannelByUserId(Integer userId, Integer enable) {
if (null == userId) {
return;
}
List<LicenseDO> licenseDOList = licenseMapper.listByUserId(userId);
if (CollectionUtil.isEmpty(licenseDOList)) {
return;
}
for (LicenseDO licenseDO : licenseDOList) {
updateVisitorChannelByLicenseId(licenseDO.getId(), enable);
}
}
/**
* 更新
* 触发时机:删除license、禁用license、启用license (新增、修改license不涉及VisitorChannel的变更)
* 重置licenseKey,不会立即影响已经连接成功的license,如果想要立即影响,请先进行禁用
* @param licenseId
*/
public void updateVisitorChannelByLicenseId(Integer licenseId, Integer enable) {
if (null == licenseId) {
return;
}
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseId);
if (null == cmdChannel) {
// 如果不存在有效的cmdChannel,则无需更新VisitorChannel
return;
}
EnableStatusEnum enableStatusEnum = EnableStatusEnum.of(enable);
List<PortMappingDO> portMappingDOList = portMappingMapper.findListByLicenseId(licenseId);
if (!CollectionUtil.isEmpty(portMappingDOList)) {
for (PortMappingDO portMappingDO : portMappingDOList) {
if (EnableStatusEnum.DISABLE == enableStatusEnum) {
removeVisitorChannelByPortMapping(portMappingDO);
} else if (EnableStatusEnum.ENABLE == EnableStatusEnum.of(portMappingDO.getEnable())) {
addVisitorChannelByPortMapping(portMappingDO);
}
}
}
}
/**
* 更新
* 触发时机:修改端口映射
* @param oldPortMappingDO
* @param newPortMappingDO
*/
public void updateVisitorChannelByPortMapping(PortMappingDO oldPortMappingDO, PortMappingDO newPortMappingDO) {
if (null == oldPortMappingDO || null == newPortMappingDO) {
return;
}
removeVisitorChannelByPortMapping(oldPortMappingDO);
addVisitorChannelByPortMapping(newPortMappingDO);
}
/**
* 新增VisitorChannel
* 触发时机:新增端口映射、启用端口映射
* @param portMappingDO
*/
public void addVisitorChannelByPortMapping(PortMappingDO portMappingDO) {
if (null == portMappingDO) {
return;
}
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(portMappingDO.getLicenseId());
if (null == cmdChannel) {
// 如果不存在有效的cmdChannel,则无需更新VisitorChannel
return;
}
// 判断端口映射是否启用
if (EnableStatusEnum.DISABLE != EnableStatusEnum.of(portMappingDO.getEnable())) {
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
// 判断license是否启用
if (null != licenseDO && EnableStatusEnum.ENABLE == EnableStatusEnum.of(licenseDO.getEnable())) {
UserDO userDO = userMapper.findById(licenseDO.getUserId());
// 判断用户是否启用
if (null != userDO && EnableStatusEnum.ENABLE == EnableStatusEnum.of(userDO.getEnable())) {
PortPoolDO portPoolDO = portPoolMapper.findByPort(portMappingDO.getServerPort());
// 判断端口池是否启用
if (null != portPoolDO && EnableStatusEnum.ENABLE == EnableStatusEnum.of(portPoolDO.getEnable())) {
// 未删除且未禁用,则开启代理
ProxyUtil.addProxyInfo(portMappingDO.getLicenseId(), ProxyMapping.build(portMappingDO));
ProxyUtil.addCmdChannel(portMappingDO.getLicenseId(), cmdChannel, Sets.newHashSet(portMappingDO.getServerPort()));
startUserPortServer(ProxyUtil.getAttachInfo(cmdChannel), Lists.newArrayList(portMappingDO));
}
}
}
}
}
/**
* 删除VisitorChannel
* 触发时机:删除端口映射、禁用端口映射
* @param portMappingDO
*/
public void removeVisitorChannelByPortMapping(PortMappingDO portMappingDO) {
if (null == portMappingDO) {
return;
}
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(portMappingDO.getLicenseId());
if (null == cmdChannel) {
// 如果不存在有效的cmdChannel,则无需更新VisitorChannel
return;
}
Channel visitorChannel = ProxyUtil.getVisitorChannelByServerPort(portMappingDO.getServerPort());
if (null != visitorChannel) {
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (null != proxyChannel) {
proxyChannel.close();
}
visitorChannel.close();
}
ProxyUtil.removeProxyInfo(portMappingDO.getServerPort());
}
private void startUserPortServer(CmdChannelAttachInfo cmdChannelAttachInfo, List<PortMappingDO> portMappingList) {
if (CollectionUtil.isEmpty(portMappingList)) {
return;
}
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(serverBossGroup, serverWorkerGroup)
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new VisitorChannelHandler());
}
});
for (PortMappingDO portMapping : portMappingList) {
try {
proxyMutualService.bindServerPort(cmdChannelAttachInfo, portMapping.getServerPort());
bootstrap.bind(portMapping.getServerPort()).get();
log.info("绑定用户端口: {}", portMapping.getServerPort());
} catch (Exception ex) {
// BindException表示该端口已经绑定过
if (!(ex.getCause() instanceof BindException)) {
throw new RuntimeException(ex);
}
}
}
}
}
@@ -21,6 +21,7 @@
*/
package fun.asgc.neutrino.proxy.server.util;
import com.google.common.collect.Sets;
import fun.asgc.neutrino.core.util.ChannelUtil;
import fun.asgc.neutrino.core.util.CollectionUtil;
import fun.asgc.neutrino.proxy.core.ChannelAttribute;
@@ -50,7 +51,7 @@ public class ProxyUtil {
/**
* 代理信息映射
*/
private static final Map<Integer, String> proxyInfoMap = new HashMap<>();
private static final Map<Integer, String> proxyInfoMap = new ConcurrentHashMap<>();
/**
* 服务端口 -> 指令通道映射
*/
@@ -59,6 +60,10 @@ public class ProxyUtil {
* license -> 指令通道映射
*/
private static Map<Integer, Channel> licenseToCmdChannelMap = new ConcurrentHashMap<>();
/**
* 服务端口 -> 访问通道映射
*/
private static Map<Integer, Channel> serverPortToVisitorChannel = new ConcurrentHashMap<>();
/**
* cmdChannelAttachInfo.getUserChannelMap() 读写锁
@@ -72,12 +77,30 @@ public class ProxyUtil {
*/
public static void initProxyInfo(Integer licenseId, List<ProxyMapping> proxyMappingList) {
licenseToServerPortMap.put(licenseId, new HashSet<>());
for (ProxyMapping proxyMapping : proxyMappingList) {
licenseToServerPortMap.get(licenseId).add(proxyMapping.getServerPort());
proxyInfoMap.put(proxyMapping.getServerPort(), proxyMapping.getLanInfo());
addProxyInfo(licenseId, proxyMappingList);
}
public static void addProxyInfo(Integer licenseId, List<ProxyMapping> proxyMappingList) {
if (!CollectionUtil.isEmpty(proxyMappingList)) {
for (ProxyMapping proxyMapping : proxyMappingList) {
licenseToServerPortMap.get(licenseId).add(proxyMapping.getServerPort());
proxyInfoMap.put(proxyMapping.getServerPort(), proxyMapping.getLanInfo());
}
}
}
public static void addProxyInfo(Integer licenseId, ProxyMapping proxyMapping) {
if (null == licenseId || null == proxyMapping) {
return;
}
licenseToServerPortMap.get(licenseId).add(proxyMapping.getServerPort());
proxyInfoMap.put(proxyMapping.getServerPort(), proxyMapping.getLanInfo());
}
public static void removeProxyInfo(Integer serverPort) {
proxyInfoMap.remove(serverPort);
}
/**
* 根据licenseId获取服务端端口集合
* @param licenseId licenseId
@@ -103,19 +126,25 @@ public class ProxyUtil {
* @param serverPorts 服务端端口集合
*/
public static void addCmdChannel(Integer licenseId, Channel cmdChannel, Set<Integer> serverPorts) {
if (CollectionUtil.isEmpty(serverPorts)) {
return;
if (!CollectionUtil.isEmpty(serverPorts)) {
for (int port : serverPorts) {
serverPortToCmdChannelMap.put(port, cmdChannel);
}
}
CmdChannelAttachInfo cmdChannelAttachInfo = getAttachInfo(cmdChannel);
if (null == cmdChannelAttachInfo) {
cmdChannelAttachInfo = new CmdChannelAttachInfo()
.setIp(ChannelUtil.getIP(cmdChannel))
.setLicenseId(licenseId)
.setVisitorChannelMap(new HashMap<>(16))
.setServerPorts(Sets.newHashSet());
setAttachInfo(cmdChannel, cmdChannelAttachInfo);
}
for (int port : serverPorts) {
serverPortToCmdChannelMap.put(port, cmdChannel);
if (!CollectionUtil.isEmpty(serverPorts)) {
cmdChannelAttachInfo.getServerPorts().addAll(serverPorts);
}
setAttachInfo(cmdChannel, new CmdChannelAttachInfo()
.setIp(ChannelUtil.getIP(cmdChannel))
.setServerPorts(serverPorts)
.setLicenseId(licenseId)
.setVisitorChannelMap(new HashMap<>(16)));
licenseToCmdChannelMap.put(licenseId, cmdChannel);
}
@@ -174,7 +203,7 @@ public class ProxyUtil {
* @param visitorId
* @param visitorChannel
*/
public static void addVisitorChannelToCmdChannel(Channel cmdChannel, String visitorId, Channel visitorChannel) {
public static void addVisitorChannelToCmdChannel(Channel cmdChannel, String visitorId, Channel visitorChannel, Integer serverPort) {
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
String lanInfo = getClientLanInfoByServerPort(sa.getPort());
CmdChannelAttachInfo cmdChannelAttachInfo = getAttachInfo(cmdChannel);
@@ -182,6 +211,7 @@ public class ProxyUtil {
setAttachInfo(visitorChannel, new VisitorChannelAttachInfo()
.setVisitorId(visitorId)
.setLanInfo(lanInfo)
.setServerPort(serverPort)
.setLicenseId(cmdChannelAttachInfo.getLicenseId())
.setIp(ChannelUtil.getIP(visitorChannel))
);
@@ -191,6 +221,7 @@ public class ProxyUtil {
} finally {
userChannelMapLock.writeLock().unlock();
}
serverPortToVisitorChannel.put(serverPort, visitorChannel);
}
public static Channel removeVisitorChannelFromCmdChannel(Channel cmdChannel, String visitorId) {
@@ -219,6 +250,15 @@ public class ProxyUtil {
return ((CmdChannelAttachInfo)getAttachInfo(cmdChannel)).getVisitorChannelMap().get(visitorId);
}
/**
* 根据服务端口获取访问通道
* @param serverPort
* @return
*/
public static Channel getVisitorChannelByServerPort(Integer serverPort) {
return serverPortToVisitorChannel.get(serverPort);
}
/**
* 获取访问者ID
*
+25 -1
View File
@@ -6,7 +6,31 @@
- 弹框展示月度明细
- 弹框展示今日流量明细
- 首页图表📈
- 1、License在线数
- 2、端口映射在线数
- 3、今日流量(上行、下行)
- 4、历史流量(上行、下行)
- 点击14 切换列表
- 在线License列表
- 用户名
- License名称
- LicenseKey
- 在线端口映射列表
- 用户名
- License名称
- 服务端口
- 代理客户端
- 今日24小时流量列表(按小时到排序)
- 用户名
- License名称
- 时间
- 流量
- 历史流量列表(取最近12个月按月到排序)
- 用户名
- License名称
- 时间
- 流量
- 今日流量折线图(上行、下行、总流量,按分钟统计0~24小时)
# Bug
- windows环境下直接运行发布版的jar包,日志输出乱码
- 部份用户windows环境下启动客户端,扫描类个数为0个