Browse Source

faet:localproxy

master
秋田弘 5 months ago
parent
commit
f5c8389b1c
  1. 23
      frontend/src/components/setting/FrpcEdit.vue
  2. 544
      frontend/src/components/setting/LocalProxy.vue
  3. 2
      frontend/src/stores/proxy.ts
  4. 11
      godo/model/local_proxy.go
  5. 56
      godo/proxy/local.go

23
frontend/src/components/setting/FrpcEdit.vue

@ -1,13 +1,13 @@
<script lang="ts" setup>
import { ChooseFileDialog,OpenDirDialog } from "@/util/goutil";
import { useProxyStore } from "@/stores/proxy";
import { ChooseFileDialog } from "@/util/goutil";
import { notifyError, notifySuccess } from "@/util/msg";
import { ref } from "vue";
const proxyStore = useProxyStore();
const { updateProxy } = proxyStore;
const proxyDialogShow = ref(false);
// const proxyDialogShow = ref(false);
const pwdRef = ref<any>(null);
//
@ -164,9 +164,10 @@
{ label: "被访问者", value: "visited" },
]);
const handleSelectFile = (type :number) => {
const handleSelectFile = (type: number) => {
//choose.select("", "*");
ChooseFileDialog().then((res) => {
console.log("res");
if (type === 1) {
proxyStore.proxyData.https2httpCaFile = res;
} else if (type === 2) {
@ -174,13 +175,13 @@
}
});
};
const handleSelectPath = (type : number) => {
OpenDirDialog().then((res) => {
if (type === 1) {
proxyStore.proxyData.localPath = res;
}
});
};
// const handleSelectPath = (type: number) => {
// OpenDirDialog().then((res) => {
// if (type === 1) {
// proxyStore.proxyData.localPath = res;
// }
// });
// };
</script>
<template>

544
frontend/src/components/setting/LocalProxy.vue

@ -1,229 +1,329 @@
<script setup lang="ts">
import { Plus } from '@element-plus/icons-vue'
import { ref } from "vue";
import { OpenDirDialog } from "@/util/goutil";
import { getSystemConfig } from '@/system/config';
import { notifySuccess, notifyError } from '@/util/msg';
interface ProxyItem {
id: number;
port: number;
proxyType: string;
domain: string;
path?: string;
status: boolean;
}
const proxyInit = {
id: Date.now(),
port: 8080,
proxyType: "http",
domain: "",
path : "",
status: true,
}
const config = getSystemConfig();
const proxies = ref<ProxyItem[]>([]);
const page = ref({
current: 1,
size: 10,
total: 0,
})
const fetchProxies = () => {
fetch(config.apiUrl + "/proxy/local/list").then(res => res.json()).then(res => {
if (res.code === 0) {
const data = res.data;
if (data.proxies && Array.isArray(data.proxies)) {
proxies.value = data.proxies;
page.value.total = data.total;
} else {
console.error('Invalid data format:', data);
}
}
import { getSystemConfig } from "@/system/config";
import { OpenDirDialog } from "@/util/goutil";
import { notifyError, notifySuccess } from "@/util/msg";
import { Plus } from "@element-plus/icons-vue";
import { ref } from "vue";
interface ProxyItem {
id: number;
port: number;
proxyType: string;
domain: string;
path?: string;
status: boolean;
listenPort: number;
}
const proxyInit = {
id: Date.now(),
port: 8080,
proxyType: "http",
domain: "", //
path: "", //
status: true,
listenPort: 80,
};
const config = getSystemConfig();
const proxies = ref<ProxyItem[]>([]);
const page = ref({
current: 1,
size: 10,
total: 0,
});
const fetchProxies = () => {
fetch(config.apiUrl + "/proxy/local/list")
.then((res) => res.json())
.then((res) => {
if (res.code === 0) {
const data = res.data;
if (data.proxies && Array.isArray(data.proxies)) {
proxies.value = data.proxies;
page.value.total = data.total;
} else {
console.error("Invalid data format:", data);
}
}
})
.catch((err) => {
console.error("Error fetching data:", err);
});
};
const createProxies = (data: ProxyItem) => {
fetch(config.apiUrl + "/proxy/local/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}).then((res) => {
if (!res.ok) {
notifyError("添加代理失败");
} else {
notifySuccess("添加代理成功");
proxyData.value = proxyInit;
proxyDialogShow.value = false;
fetchProxies();
}
});
};
const updateProxies = (data: ProxyItem) => {
console.log(data);
fetch(config.apiUrl + "/proxy/local/update", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}).then((res) => {
if (!res.ok) {
notifyError("保存代理失败");
} else {
notifySuccess("保存代理成功");
proxyData.value = proxyInit;
proxyDialogShow.value = false;
fetchProxies();
}
});
};
const DeleteProxy = (id: number) => {
fetch(config.apiUrl + "/proxy/local/delete?id=" + id).then((res) => {
if (!res.ok) {
notifyError("删除代理失败");
} else {
notifySuccess("删除代理成功");
fetchProxies();
}
});
};
const SetStatus = (id: number) => {
fetch(config.apiUrl + "/proxy/local/status?id=" + id).then((res) => {
if (!res.ok) {
notifyError("设置代理状态失败");
} else {
notifySuccess("设置代理状态成功");
fetchProxies();
}
});
};
const changePage = (current: number) => {
page.value.current = current;
fetchProxies();
};
onMounted(() => {
fetchProxies();
});
}).catch(err => {
console.error('Error fetching data:', err);
});
};
const createProxies = (data: ProxyItem) => {
fetch(config.apiUrl + '/proxy/local/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then(res => {
if (!res.ok) {
notifyError('添加代理失败');
} else {
notifySuccess('添加代理成功');
proxyData.value = proxyInit;
proxyDialogShow.value = false;
fetchProxies()
}
});
};
const updateProxies = (data: ProxyItem) => {
fetch(config.apiUrl + '/proxy/local/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then(res => {
if (!res.ok) {
notifyError('保存代理失败');
} else {
notifySuccess('保存代理成功');
proxyData.value = proxyInit;
proxyDialogShow.value = false;
fetchProxies()
}
});
};
const DeleteProxy = (id: number) => {
fetch(config.apiUrl + '/proxy/local/delete?id=' + id).then(res => {
if (!res.ok) {
notifyError('删除代理失败');
} else {
notifySuccess('删除代理成功');
fetchProxies()
}
});
}
const SetStatus = (id: number) => {
fetch(config.apiUrl + '/proxy/local/status?id=' + id).then(res => {
if (!res.ok) {
notifyError('设置代理状态失败');
} else {
notifySuccess('设置代理状态成功');
fetchProxies()
}
});
}
const changePage = (current: number) => {
page.value.current = current;
fetchProxies();
};
onMounted(() => {
fetchProxies();
});
const proxyData = ref<ProxyItem>(proxyInit);
const types = ref([
{ label: "HTTP", value: "http" },
{ label: "Udp", value: "udp" },
{ label: "静态文件访问", value: "file" },
]);
const proxyDialogShow = ref(false);
const isEditing = ref(false);
const pwdRef = ref<any>(null);
const editProxy = (proxy: ProxyItem) => {
proxyData.value = { ...proxy };
isEditing.value = true;
proxyDialogShow.value = true;
};
const addProxy = () => {
console.log(proxyData.value);
proxyData.value = { ...proxyInit, id: Date.now() };
isEditing.value = false;
proxyDialogShow.value = true;
};
const proxyData = ref<ProxyItem>(proxyInit);
const types = ref([
{ label: 'HTTP', value: 'http' },
{ label: 'Udp', value: 'udp' },
{ label: '静态文件访问', value: 'file' },
])
const proxyDialogShow = ref(false);
const isEditing = ref(false);
const pwdRef = ref<any>(null);
const editProxy = (proxy: ProxyItem) => {
proxyData.value = { ...proxy };
isEditing.value = true;
proxyDialogShow.value = true;
};
const addProxy = () => {
proxyData.value = proxyInit;
isEditing.value = false;
proxyDialogShow.value = true;
};
function selectFile() {
OpenDirDialog().then((res: string) => {
proxyData.value.domain = res;
});
}
const saveProxy = () => {
pwdRef.value.validate((valid: boolean) => {
if (valid) {
proxyData.value.port = Number(proxyData.value.port)
if (isEditing.value) {
updateProxies(proxyData.value)
} else {
createProxies(proxyData.value)
}
} else {
console.log('表单验证失败');
}
});
};
const proxyRules = {
port: [
{ required: true, message: '端口不能为空', trigger: 'blur' },
{ pattern: /^(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$/, message: '请输入有效的端口号(1-65535)', trigger: 'blur' }
],
domain: [
{ required: true, message: '代理域名不能为空', trigger: 'blur' },
{
pattern: /^(https?:\/\/)?((?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|localhost)(:\d{1,5})?(\/[^\s]*)?$/,
message: '请输入有效的域名格式',
trigger: 'blur'
}
]
};
function selectFile() {
OpenDirDialog().then((res: string) => {
proxyData.value.domain = res;
});
}
const saveProxy = () => {
pwdRef.value.validate((valid: boolean) => {
if (valid) {
proxyData.value.port = Number(proxyData.value.port);
proxyData.value.listenPort = Number(proxyData.value.listenPort);
if (isEditing.value) {
updateProxies(proxyData.value);
} else {
createProxies(proxyData.value);
}
} else {
console.log("表单验证失败");
}
});
};
const proxyRules = {
port: [
{ required: true, message: "端口不能为空", trigger: "blur" },
{
pattern:
/^(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$/,
message: "请输入有效的端口号(1-65535)",
trigger: "blur",
},
],
domain: [
{ required: true, message: "代理域名不能为空", trigger: "blur" },
{
pattern:
/^(https?:\/\/)?((?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|localhost)(:\d{1,5})?(\/[^\s]*)?$/,
message: "请输入有效的域名格式",
trigger: "blur",
},
],
};
</script>
<template>
<div>
<el-row justify="end">
<el-button type="primary" :icon="Plus" circle @click="addProxy" />
</el-row>
<el-table :data="proxies" style="width: 98%;border:none">
<el-table-column prop="proxyType" label="代理类型" width="80" />
<el-table-column prop="port" label="本地端口" width="80" />
<!-- <el-table-column prop="domain" label="代理域名" /> -->
<el-table-column label="状态">
<template #default="scope">
<!-- <el-switch v-model="scope.row.status" active-color="#ff4949" inactive-color="#13ce66" @change="SetStatus(scope.row.id)"></el-switch> -->
<el-button size="small" @click="SetStatus(scope.row.id)">{{scope.row.status ? '启用' : '禁用'}}</el-button>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button size="small" circle icon="Edit" @click="editProxy(scope.row)"></el-button>
<el-button size="small" circle icon="Delete" @click="DeleteProxy(scope.row.id)"></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-if="page.total > page.size" layout="prev, pager, next" :total="page.total"
:page-size="page.size" v-model:current-page="page.current" @current-change="changePage" />
<el-dialog v-model="proxyDialogShow" :title="isEditing ? '编辑代理' : '添加代理'" width="400px">
<span>
<el-form :model="proxyData" :rules="proxyRules" ref="pwdRef">
<el-form-item label="代理类型" prop="type">
<el-select v-model="proxyData.proxyType" placeholder="代理类型">
<el-option v-for="type in types" :key="type.value" :label="type.label"
:value="type.value" />
</el-select>
</el-form-item>
<el-form-item label="本地端口" prop="port">
<el-input v-model="proxyData.port" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch v-model="proxyData.status" active-color="#13ce66" inactive-color="#ff4949"
active-text="启用" inactive-text="禁用" />
</el-form-item>
<el-form-item label="代理域名" prop="domain" v-if="proxyData.proxyType === 'http'">
<el-input v-model="proxyData.domain" />
</el-form-item>
<el-form-item label="文件路径" prop="path" v-if="proxyData.proxyType === 'file'">
<el-input v-model="proxyData.path" @click="selectFile()" />
</el-form-item>
<el-form-item label="转发IP+端口" prop="domain" v-if="proxyData.proxyType === 'udp'">
<el-input v-model="proxyData.domain" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveProxy" style="margin: 0 auto;">
确认
</el-button>
</el-form-item>
</el-form>
</span>
</el-dialog>
</div>
</template>
<div>
<el-row justify="end">
<el-button
type="primary"
:icon="Plus"
circle
@click="addProxy"
/>
</el-row>
<el-table
:data="proxies"
style="width: 98%; border: none"
>
<el-table-column
prop="proxyType"
label="代理类型"
width="80"
/>
<el-table-column
prop="port"
label="本地端口"
width="80"
/>
<!-- <el-table-column prop="domain" label="代理域名" /> -->
<el-table-column label="状态">
<template #default="scope">
<!-- <el-switch v-model="scope.row.status" active-color="#ff4949" inactive-color="#13ce66" @change="SetStatus(scope.row.id)"></el-switch> -->
<el-button
size="small"
@click="SetStatus(scope.row.id)"
>{{ scope.row.status ? "启用" : "禁用" }}</el-button
>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button
size="small"
circle
icon="Edit"
@click="editProxy(scope.row)"
></el-button>
<el-button
size="small"
circle
icon="Delete"
@click="DeleteProxy(scope.row.id)"
></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-if="page.total > page.size"
layout="prev, pager, next"
:total="page.total"
:page-size="page.size"
v-model:current-page="page.current"
@current-change="changePage"
/>
<el-dialog
v-model="proxyDialogShow"
:title="isEditing ? '编辑代理' : '添加代理'"
width="400px"
>
<span>
<el-form
:model="proxyData"
:rules="proxyRules"
ref="pwdRef"
>
<el-form-item
label="代理类型"
prop="type"
>
<el-select
v-model="proxyData.proxyType"
placeholder="代理类型"
>
<el-option
v-for="type in types"
:key="type.value"
:label="type.label"
:value="type.value"
/>
</el-select>
</el-form-item>
<el-form-item
label="本地端口"
prop="port"
>
<el-input v-model="proxyData.port" />
</el-form-item>
<el-form-item
label="状态"
prop="status"
>
<el-switch
v-model="proxyData.status"
active-color="#13ce66"
inactive-color="#ff4949"
active-text="启用"
inactive-text="禁用"
/>
</el-form-item>
<div v-if="proxyData.proxyType === 'http'">
<el-form-item
label="代理域名"
prop="domain"
>
<el-input v-model="proxyData.domain" />
</el-form-item>
<el-form-item
label="代理端口"
prop="listenPort"
>
<el-input v-model="proxyData.listenPort" />
</el-form-item>
</div>
<el-form-item
label="文件路径"
prop="path"
v-if="proxyData.proxyType === 'file'"
>
<el-input
v-model="proxyData.path"
@click="selectFile()"
/>
</el-form-item>
<el-form-item
label="转发IP+端口"
prop="domain"
v-if="proxyData.proxyType === 'udp'"
>
<el-input v-model="proxyData.domain" />
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="saveProxy"
style="margin: 0 auto"
>
确认
</el-button>
</el-form-item>
</el-form>
</span>
</el-dialog>
</div>
</template>

2
frontend/src/stores/proxy.ts

@ -186,7 +186,7 @@ export const useProxyStore = defineStore('proxyStore', () => {
return await fetch(`${apiUrl}/proxy/frpc/getconfig`).then(res => res.json())
}
const setConfig = async (config: any) => {
return await fetch(`${apiUrl}/frpc/setconfig`, {
return await fetch(`${apiUrl}/proxy/frpc/setconfig`, {
method: "POST",
headers: {
"Content-Type": "application/json"

11
godo/model/local_proxy.go

@ -2,11 +2,12 @@ package model
type LocalProxy struct {
BaseModel
Port uint `json:"port"`
ProxyType string `json:"proxyType"`
Domain string `json:"domain"`
Path string `json:"path"`
Status bool `json:"status"`
Port uint `json:"port"` // 本地端口
ProxyType string `json:"proxyType"` // 代理类型
Domain string `json:"domain"` // 代理域名
Path string `json:"path"` // 代理路径
Status bool `json:"status"` // 状态
ListenPort uint `json:"listenPort"` // 代理端口
}
func (*LocalProxy) TableName() string {

56
godo/proxy/local.go

@ -116,10 +116,11 @@ func UpdateLocalProxyHandler(w http.ResponseWriter, r *http.Request) {
return
}
updata := map[string]interface{}{
"port": lp.Port,
"proxy_type": lp.ProxyType,
"domain": lp.Domain,
"status": lp.Status,
"port": lp.Port,
"proxy_type": lp.ProxyType,
"domain": lp.Domain,
"status": lp.Status,
"listen_port": lp.ListenPort,
// path
}
@ -158,6 +159,8 @@ func DeleteLocalProxyHandler(w http.ResponseWriter, r *http.Request) {
libs.SuccessMsg(w, nil, "delete proxy success")
}
// HandlerSetProxyStatus 设置代理状态
func HandlerSetProxyStatus(w http.ResponseWriter, r *http.Request) {
idStr := r.URL.Query().Get("id")
if idStr == "" {
@ -270,37 +273,52 @@ func stopProxy(id uint) {
// HTTP 代理处理函数
func httpProxyHandler(proxy model.LocalProxy) {
remote, err := url.Parse(proxy.Domain)
// 如果 ListenPort 没有传递,默认为 80
if proxy.ListenPort == 0 {
proxy.ListenPort = 80
}
fmt.Printf("Initializing HTTP proxy for ID %d on domain %s and listen port %d\n", proxy.ID, proxy.Domain, proxy.ListenPort)
// 使用 proxy.Port 作为本地目标端口
remote, err := url.Parse(fmt.Sprintf("http://localhost:%d", proxy.Port))
if err != nil {
fmt.Printf("Failed to parse remote URL for port %d: %v\n", proxy.Port, err)
return
}
fmt.Printf("Parsed remote URL: %s\n", remote.String())
if remote.Scheme == "" {
fmt.Printf("Remote URL for port %d does not contain a scheme (http/https): %s\n", proxy.Port, proxy.Domain)
return
reverseProxy := httputil.NewSingleHostReverseProxy(remote)
reverseProxy.Director = func(req *http.Request) {
req.URL.Scheme = remote.Scheme
req.URL.Host = remote.Host
req.Host = remote.Host
req.URL.Path = proxy.Path + req.URL.Path // 使用代理路径
fmt.Printf("Proxying request to: %s\n", req.URL.String())
}
reverseProxy := httputil.NewSingleHostReverseProxy(remote)
reverseProxy.ModifyResponse = func(resp *http.Response) error {
fmt.Printf("Received response with status: %s\n", resp.Status)
return nil
}
// 设置请求头
// reverseProxy.Director = func(req *http.Request) {
// req.Header.Add("X-Forwarded-For", req.RemoteAddr)
// req.Header.Add("X-Real-IP", req.RemoteAddr)
// req.Host = remote.Host
// }
reverseProxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
fmt.Printf("Error during proxying: %v\n", err)
http.Error(rw, "Proxy error", http.StatusBadGateway)
}
// 启动 HTTP 服务器并监听指定端口
// 监听配置中指定的域名和端口
server := &http.Server{
Addr: fmt.Sprintf(":%d", proxy.Port),
Addr: fmt.Sprintf(":%d", proxy.ListenPort),
Handler: reverseProxy,
}
proxyServers.Store(proxy.ID, ProxyServer{Type: "http", Server: server})
fmt.Printf("Starting HTTP proxy on LocalHost port %d and forwarding to %s\n", proxy.Port, proxy.Domain)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("Failed to start HTTP proxy on port %d: %v\n", proxy.Port, err)
fmt.Printf("Failed to start HTTP proxy on domain %s and listen port %d: %v\n", proxy.Domain, proxy.ListenPort, err)
} else {
fmt.Printf("HTTP proxy on domain %s and listen port %d started successfully\n", proxy.Domain, proxy.ListenPort)
}
}

Loading…
Cancel
Save