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> <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);
// //
@ -164,9 +164,10 @@
{ label: "被访问者", value: "visited" }, { label: "被访问者", value: "visited" },
]); ]);
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>

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

@ -1,229 +1,329 @@
<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 { notifyError, notifySuccess } from "@/util/msg";
import { getSystemConfig } from '@/system/config'; import { Plus } from "@element-plus/icons-vue";
import { notifySuccess, notifyError } from '@/util/msg'; import { ref } from "vue";
interface ProxyItem { interface ProxyItem {
id: number; id: number;
port: number; port: number;
proxyType: string; proxyType: string;
domain: string; domain: string;
path?: string; path?: string;
status: boolean; status: boolean;
} listenPort: number;
const proxyInit = { }
id: Date.now(), const proxyInit = {
port: 8080, id: Date.now(),
proxyType: "http", port: 8080,
domain: "", proxyType: "http",
path : "", domain: "", //
status: true, path: "", //
} status: true,
const config = getSystemConfig(); listenPort: 80,
const proxies = ref<ProxyItem[]>([]); };
const page = ref({ const config = getSystemConfig();
current: 1, const proxies = ref<ProxyItem[]>([]);
size: 10, const page = ref({
total: 0, current: 1,
}) size: 10,
const fetchProxies = () => { total: 0,
fetch(config.apiUrl + "/proxy/local/list").then(res => res.json()).then(res => { });
if (res.code === 0) { const fetchProxies = () => {
const data = res.data; fetch(config.apiUrl + "/proxy/local/list")
if (data.proxies && Array.isArray(data.proxies)) { .then((res) => res.json())
proxies.value = data.proxies; .then((res) => {
page.value.total = data.total; if (res.code === 0) {
} else { const data = res.data;
console.error('Invalid data format:', 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 => { const proxyData = ref<ProxyItem>(proxyInit);
console.error('Error fetching data:', err); const types = ref([
}); { label: "HTTP", value: "http" },
}; { label: "Udp", value: "udp" },
const createProxies = (data: ProxyItem) => { { label: "静态文件访问", value: "file" },
fetch(config.apiUrl + '/proxy/local/create', { ]);
method: 'POST', const proxyDialogShow = ref(false);
headers: { const isEditing = ref(false);
'Content-Type': 'application/json' const pwdRef = ref<any>(null);
},
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 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); function selectFile() {
const types = ref([ OpenDirDialog().then((res: string) => {
{ label: 'HTTP', value: 'http' }, proxyData.value.domain = res;
{ label: 'Udp', value: 'udp' }, });
{ label: '静态文件访问', value: 'file' }, }
]) const saveProxy = () => {
const proxyDialogShow = ref(false); pwdRef.value.validate((valid: boolean) => {
const isEditing = ref(false); if (valid) {
const pwdRef = ref<any>(null); proxyData.value.port = Number(proxyData.value.port);
proxyData.value.listenPort = Number(proxyData.value.listenPort);
if (isEditing.value) {
const editProxy = (proxy: ProxyItem) => { updateProxies(proxyData.value);
proxyData.value = { ...proxy }; } else {
isEditing.value = true; createProxies(proxyData.value);
proxyDialogShow.value = true; }
}; } else {
const addProxy = () => { console.log("表单验证失败");
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'
}
]
};
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> </script>
<template> <template>
<div> <div>
<el-row justify="end"> <el-row justify="end">
<el-button type="primary" :icon="Plus" circle @click="addProxy" /> <el-button
</el-row> type="primary"
<el-table :data="proxies" style="width: 98%;border:none"> :icon="Plus"
<el-table-column prop="proxyType" label="代理类型" width="80" /> circle
<el-table-column prop="port" label="本地端口" width="80" /> @click="addProxy"
<!-- <el-table-column prop="domain" label="代理域名" /> --> />
</el-row>
<el-table-column label="状态"> <el-table
<template #default="scope"> :data="proxies"
<!-- <el-switch v-model="scope.row.status" active-color="#ff4949" inactive-color="#13ce66" @change="SetStatus(scope.row.id)"></el-switch> --> style="width: 98%; border: none"
<el-button size="small" @click="SetStatus(scope.row.id)">{{scope.row.status ? '启用' : '禁用'}}</el-button> >
</template> <el-table-column
</el-table-column> prop="proxyType"
<el-table-column label="操作"> label="代理类型"
<template #default="scope"> width="80"
<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> <el-table-column
</template> prop="port"
</el-table-column> label="本地端口"
</el-table> width="80"
<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-table-column prop="domain" label="代理域名" /> -->
<el-dialog v-model="proxyDialogShow" :title="isEditing ? '编辑代理' : '添加代理'" width="400px">
<span> <el-table-column label="状态">
<el-form :model="proxyData" :rules="proxyRules" ref="pwdRef"> <template #default="scope">
<el-form-item label="代理类型" prop="type"> <!-- <el-switch v-model="scope.row.status" active-color="#ff4949" inactive-color="#13ce66" @change="SetStatus(scope.row.id)"></el-switch> -->
<el-select v-model="proxyData.proxyType" placeholder="代理类型"> <el-button
<el-option v-for="type in types" :key="type.value" :label="type.label" size="small"
:value="type.value" /> @click="SetStatus(scope.row.id)"
</el-select> >{{ scope.row.status ? "启用" : "禁用" }}</el-button
</el-form-item> >
<el-form-item label="本地端口" prop="port"> </template>
<el-input v-model="proxyData.port" /> </el-table-column>
</el-form-item> <el-table-column label="操作">
<el-form-item label="状态" prop="status"> <template #default="scope">
<el-switch v-model="proxyData.status" active-color="#13ce66" inactive-color="#ff4949" <el-button
active-text="启用" inactive-text="禁用" /> size="small"
</el-form-item> circle
<el-form-item label="代理域名" prop="domain" v-if="proxyData.proxyType === 'http'"> icon="Edit"
<el-input v-model="proxyData.domain" /> @click="editProxy(scope.row)"
</el-form-item> ></el-button>
<el-form-item label="文件路径" prop="path" v-if="proxyData.proxyType === 'file'"> <el-button
<el-input v-model="proxyData.path" @click="selectFile()" /> size="small"
</el-form-item> circle
<el-form-item label="转发IP+端口" prop="domain" v-if="proxyData.proxyType === 'udp'"> icon="Delete"
<el-input v-model="proxyData.domain" /> @click="DeleteProxy(scope.row.id)"
</el-form-item> ></el-button>
<el-form-item> </template>
<el-button type="primary" @click="saveProxy" style="margin: 0 auto;"> </el-table-column>
确认 </el-table>
</el-button> <el-pagination
</el-form-item> v-if="page.total > page.size"
</el-form> layout="prev, pager, next"
</span> :total="page.total"
</el-dialog> :page-size="page.size"
</div> v-model:current-page="page.current"
</template> @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()) 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 {

56
godo/proxy/local.go

@ -116,10 +116,11 @@ func UpdateLocalProxyHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
updata := map[string]interface{}{ updata := map[string]interface{}{
"port": lp.Port, "port": lp.Port,
"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