Browse Source

faet:localproxy

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

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

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

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

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

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()) return await fetch(`${apiUrl}/proxy/frpc/getconfig`).then(res => res.json())
} }
const setConfig = async (config: any) => { const setConfig = async (config: any) => {
return await fetch(`${apiUrl}/frpc/setconfig`, { return await fetch(`${apiUrl}/proxy/frpc/setconfig`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"

11
godo/model/local_proxy.go

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

48
godo/proxy/local.go

@ -120,6 +120,7 @@ func UpdateLocalProxyHandler(w http.ResponseWriter, r *http.Request) {
"proxy_type": lp.ProxyType, "proxy_type": lp.ProxyType,
"domain": lp.Domain, "domain": lp.Domain,
"status": lp.Status, "status": lp.Status,
"listen_port": lp.ListenPort,
// path // path
} }
@ -158,6 +159,8 @@ func DeleteLocalProxyHandler(w http.ResponseWriter, r *http.Request) {
libs.SuccessMsg(w, nil, "delete proxy success") libs.SuccessMsg(w, nil, "delete proxy success")
} }
// HandlerSetProxyStatus 设置代理状态
func HandlerSetProxyStatus(w http.ResponseWriter, r *http.Request) { func HandlerSetProxyStatus(w http.ResponseWriter, r *http.Request) {
idStr := r.URL.Query().Get("id") idStr := r.URL.Query().Get("id")
if idStr == "" { if idStr == "" {
@ -270,37 +273,52 @@ func stopProxy(id uint) {
// HTTP 代理处理函数 // HTTP 代理处理函数
func httpProxyHandler(proxy model.LocalProxy) { 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 { if err != nil {
fmt.Printf("Failed to parse remote URL for port %d: %v\n", proxy.Port, err) fmt.Printf("Failed to parse remote URL for port %d: %v\n", proxy.Port, err)
return return
} }
fmt.Printf("Parsed remote URL: %s\n", remote.String())
if remote.Scheme == "" { reverseProxy := httputil.NewSingleHostReverseProxy(remote)
fmt.Printf("Remote URL for port %d does not contain a scheme (http/https): %s\n", proxy.Port, proxy.Domain) reverseProxy.Director = func(req *http.Request) {
return 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.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
// reverseProxy.Director = func(req *http.Request) { fmt.Printf("Error during proxying: %v\n", err)
// req.Header.Add("X-Forwarded-For", req.RemoteAddr) http.Error(rw, "Proxy error", http.StatusBadGateway)
// req.Header.Add("X-Real-IP", req.RemoteAddr) }
// req.Host = remote.Host
// }
// 启动 HTTP 服务器并监听指定端口 // 监听配置中指定的域名和端口
server := &http.Server{ server := &http.Server{
Addr: fmt.Sprintf(":%d", proxy.Port), Addr: fmt.Sprintf(":%d", proxy.ListenPort),
Handler: reverseProxy, Handler: reverseProxy,
} }
proxyServers.Store(proxy.ID, ProxyServer{Type: "http", Server: server}) 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 { 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