update: 2023-03-15

1. 添加URL重写器
2. 添加插件系统
This commit is contained in:
2025-03-14 00:30:20 +08:00
parent 4ac5aab7a0
commit 7affdc79c6
13 changed files with 2238 additions and 0 deletions

View File

@@ -0,0 +1,460 @@
package main
import (
"context"
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"runtime"
"strconv"
"strings"
"github.com/darkit/goproxy/examples/plugin"
)
// 全局插件管理器
var pm *plugin.PluginManager
// AdminHandler 处理管理界面主页
func AdminHandler(w http.ResponseWriter, r *http.Request) {
// HTML模板
tmpl := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>插件管理系统</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
.plugin-card {
border: 1px solid #ddd;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
background-color: #f9f9f9;
}
.plugin-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.plugin-title { margin: 0; color: #333; }
.plugin-version { color: #888; font-size: 0.9em; }
.plugin-author { color: #888; font-size: 0.9em; }
.plugin-desc { margin: 10px 0; }
.plugin-status {
display: inline-block;
padding: 5px 10px;
border-radius: 3px;
font-size: 0.8em;
}
.status-enabled { background-color: #dff0d8; color: #3c763d; }
.status-disabled { background-color: #f2dede; color: #a94442; }
.plugin-actions { margin-top: 15px; }
button {
padding: 5px 10px;
margin-right: 5px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 0.9em;
}
.btn-enable { background-color: #5cb85c; color: white; }
.btn-disable { background-color: #d9534f; color: white; }
.btn-config { background-color: #5bc0de; color: white; }
.config-section {
margin-top: 10px;
padding: 10px;
background-color: #f5f5f5;
border-radius: 3px;
}
.config-item {
margin: 5px 0;
display: flex;
}
.config-key {
font-weight: bold;
width: 150px;
}
.system-info {
background-color: #e9ecef;
padding: 10px;
border-radius: 5px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<h1>插件管理系统</h1>
<div class="system-info">
<h3>系统信息</h3>
<p>操作系统: {{.OS}}</p>
<p>动态加载支持: {{if .DynamicLoadingSupported}}{{else}}{{end}}</p>
<p>插件目录: {{.PluginsDir}}</p>
<p>已加载插件数量: {{len .Plugins}}</p>
</div>
<h2>已安装插件</h2>
{{range .Plugins}}
<div class="plugin-card">
<div class="plugin-header">
<div>
<h3 class="plugin-title">{{.Name}}</h3>
<span class="plugin-version">版本: {{.Version}}</span>
<span class="plugin-author">作者: {{.Author}}</span>
</div>
<div>
{{if .Enabled}}
<span class="plugin-status status-enabled">已启用</span>
{{else}}
<span class="plugin-status status-disabled">已禁用</span>
{{end}}
</div>
</div>
<div class="plugin-desc">{{.Description}}</div>
{{if .Config}}
<div class="config-section">
<h4>配置:</h4>
{{range $key, $value := .Config}}
<div class="config-item">
<div class="config-key">{{$key}}:</div>
<div class="config-value">{{$value}}</div>
</div>
{{end}}
</div>
{{end}}
<div class="plugin-actions">
{{if .Enabled}}
<a href="/disable/{{.Name}}"><button class="btn-disable">禁用</button></a>
{{else}}
<a href="/enable/{{.Name}}"><button class="btn-enable">启用</button></a>
{{end}}
<a href="/config/{{.Name}}"><button class="btn-config">配置</button></a>
</div>
</div>
{{end}}
{{if not .Plugins}}
<p>没有已安装的插件。</p>
{{end}}
<h2>操作</h2>
<form action="/reload" method="post">
<button type="submit">重新加载插件</button>
</form>
</body>
</html>
`
// 解析模板
t, err := template.New("admin").Parse(tmpl)
if err != nil {
http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
return
}
// 准备数据
data := struct {
Plugins []plugin.PluginInfo
OS string
DynamicLoadingSupported bool
PluginsDir string
}{
Plugins: pm.GetPluginInfos(),
OS: strings.Title(strings.ToLower(fmt.Sprintf("%s", strings.Split(runtime.GOOS, "/")[0]))),
DynamicLoadingSupported: pm.IsDynamicLoadingSupported(),
PluginsDir: pluginsDir,
}
// 渲染模板
if err := t.Execute(w, data); err != nil {
http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
}
}
// EnablePluginHandler 启用插件
func EnablePluginHandler(w http.ResponseWriter, r *http.Request) {
pluginName := r.URL.Path[len("/enable/"):]
if err := pm.EnablePlugin(pluginName); err != nil {
http.Error(w, fmt.Sprintf("启用插件失败: %v", err), http.StatusInternalServerError)
return
}
// 尝试初始化和启动插件
ctx := context.Background()
plugin, exists := pm.GetPlugin(pluginName)
if exists && plugin.IsEnabled() {
config, _ := pm.GetPluginConfig(pluginName)
if err := plugin.Init(ctx, config); err != nil {
http.Error(w, fmt.Sprintf("初始化插件失败: %v", err), http.StatusInternalServerError)
return
}
if err := plugin.Start(ctx); err != nil {
http.Error(w, fmt.Sprintf("启动插件失败: %v", err), http.StatusInternalServerError)
return
}
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// DisablePluginHandler 禁用插件
func DisablePluginHandler(w http.ResponseWriter, r *http.Request) {
pluginName := r.URL.Path[len("/disable/"):]
// 先停止插件
ctx := context.Background()
plugin, exists := pm.GetPlugin(pluginName)
if exists && plugin.IsEnabled() {
if err := plugin.Stop(ctx); err != nil {
http.Error(w, fmt.Sprintf("停止插件失败: %v", err), http.StatusInternalServerError)
return
}
}
// 禁用插件
if err := pm.DisablePlugin(pluginName); err != nil {
http.Error(w, fmt.Sprintf("禁用插件失败: %v", err), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// ConfigPluginHandler 配置插件
func ConfigPluginHandler(w http.ResponseWriter, r *http.Request) {
pluginName := r.URL.Path[len("/config/"):]
if r.Method == http.MethodPost {
// 处理配置表单提交
if err := r.ParseForm(); err != nil {
http.Error(w, fmt.Sprintf("解析表单失败: %v", err), http.StatusBadRequest)
return
}
// 收集配置项
config := make(map[string]interface{})
for key, values := range r.PostForm {
if key == "pluginName" {
continue // 跳过插件名称字段
}
if len(values) > 0 {
// 尝试将值转换为适当的类型
value := values[0]
// 布尔值处理
if value == "true" {
config[key] = true
} else if value == "false" {
config[key] = false
} else if strings.Contains(value, ".") {
// 尝试解析为浮点数
if f, err := strconv.ParseFloat(value, 64); err == nil {
config[key] = f
} else {
config[key] = value
}
} else if i, err := strconv.Atoi(value); err == nil {
// 尝试解析为整数
config[key] = i
} else {
// 默认为字符串
config[key] = value
}
}
}
// 保存配置
if err := pm.SetPluginConfig(pluginName, config); err != nil {
http.Error(w, fmt.Sprintf("保存配置失败: %v", err), http.StatusInternalServerError)
return
}
// 重定向回主页
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
// 获取插件信息和配置
info, config := getPluginInfo(pluginName)
if info == nil {
http.Error(w, "插件不存在", http.StatusNotFound)
return
}
// 配置表单模板
tmpl := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>配置 {{.Info.Name}}</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"], input[type="number"] { width: 100%; padding: 8px; box-sizing: border-box; }
input[type="checkbox"] { margin-right: 5px; }
button { padding: 10px 15px; background-color: #4CAF50; color: white; border: none; cursor: pointer; }
button:hover { background-color: #45a049; }
.back-link { margin-top: 20px; display: block; }
</style>
</head>
<body>
<h1>配置 {{.Info.Name}}</h1>
<form action="/config/{{.Info.Name}}" method="post">
<input type="hidden" name="pluginName" value="{{.Info.Name}}">
{{range $key, $value := .Config}}
<div class="form-group">
<label for="{{$key}}">{{$key}}:</label>
{{if eq (printf "%T" $value) "bool"}}
<input type="checkbox" id="{{$key}}" name="{{$key}}" {{if $value}}checked{{end}}>
{{else if eq (printf "%T" $value) "float64"}}
<input type="number" id="{{$key}}" name="{{$key}}" value="{{$value}}" step="0.01">
{{else if eq (printf "%T" $value) "int"}}
<input type="number" id="{{$key}}" name="{{$key}}" value="{{$value}}">
{{else}}
<input type="text" id="{{$key}}" name="{{$key}}" value="{{$value}}">
{{end}}
</div>
{{end}}
<button type="submit">保存配置</button>
</form>
<a href="/" class="back-link">返回插件列表</a>
</body>
</html>
`
// 解析模板
t, err := template.New("config").Parse(tmpl)
if err != nil {
http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
return
}
// 准备数据
data := struct {
Info *plugin.PluginInfo
Config map[string]interface{}
}{
Info: info,
Config: config,
}
// 渲染模板
if err := t.Execute(w, data); err != nil {
http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
}
}
// ReloadPluginsHandler 重新加载插件
func ReloadPluginsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
// 停止所有插件
ctx := context.Background()
if err := pm.StopPlugins(ctx); err != nil {
http.Error(w, fmt.Sprintf("停止插件失败: %v", err), http.StatusInternalServerError)
return
}
// 重新加载插件
if err := pm.LoadPlugins(); err != nil {
http.Error(w, fmt.Sprintf("加载插件失败: %v", err), http.StatusInternalServerError)
return
}
// 初始化和启动插件
if err := pm.InitPlugins(ctx); err != nil {
http.Error(w, fmt.Sprintf("初始化插件失败: %v", err), http.StatusInternalServerError)
return
}
if err := pm.StartPlugins(ctx); err != nil {
http.Error(w, fmt.Sprintf("启动插件失败: %v", err), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// API接口处理器 - 返回插件列表
func APIPluginsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
plugins := pm.GetPluginInfos()
if err := json.NewEncoder(w).Encode(plugins); err != nil {
http.Error(w, fmt.Sprintf("编码JSON失败: %v", err), http.StatusInternalServerError)
}
}
// getPluginInfo 获取插件信息和配置
func getPluginInfo(name string) (*plugin.PluginInfo, map[string]interface{}) {
plugins := pm.GetPluginInfos()
for _, info := range plugins {
if info.Name == name {
config, _ := pm.GetPluginConfig(name)
return &info, config
}
}
return nil, nil
}
// 插件目录
var pluginsDir = "./plugins"
func main3() {
// 创建插件管理器
pm = plugin.NewPluginManager(pluginsDir)
// 加载插件
if err := pm.LoadPlugins(); err != nil {
log.Fatalf("加载插件失败: %v", err)
}
// 初始化并启动插件
ctx := context.Background()
if err := pm.InitPlugins(ctx); err != nil {
log.Printf("初始化插件失败: %v", err)
}
if err := pm.StartPlugins(ctx); err != nil {
log.Printf("启动插件失败: %v", err)
}
// 注册处理器
http.HandleFunc("/", AdminHandler)
http.HandleFunc("/enable/", EnablePluginHandler)
http.HandleFunc("/disable/", DisablePluginHandler)
http.HandleFunc("/config/", ConfigPluginHandler)
http.HandleFunc("/reload", ReloadPluginsHandler)
http.HandleFunc("/api/plugins", APIPluginsHandler)
// 启动Web服务器
log.Println("管理界面启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}

View File

@@ -0,0 +1,91 @@
package plugin
// IPlugin 插件接口
// 这个文件定义了所有插件必须实现的接口
// 注意:这个文件应该与实际插件代码一起编译
type IPlugin interface {
// Name 插件名称
Name() string
// Version 插件版本
Version() string
// Description 插件描述
Description() string
// Author 插件作者
Author() string
// Init 初始化插件
Init(config map[string]interface{}) error
// Start 启动插件
Start() error
// Stop 停止插件
Stop() error
// IsEnabled 插件是否启用
IsEnabled() bool
// SetEnabled 设置插件启用状态
SetEnabled(enabled bool)
}
// BasePlugin 提供插件接口的基本实现
// 插件开发者可以嵌入此结构体,以减少需要实现的方法数量
type BasePlugin struct {
name string
version string
description string
author string
enabled bool
}
// NewBasePlugin 创建一个基本插件
func NewBasePlugin(name, version, description, author string) *BasePlugin {
return &BasePlugin{
name: name,
version: version,
description: description,
author: author,
enabled: true,
}
}
// Name 获取插件名称
func (p *BasePlugin) Name() string {
return p.name
}
// Version 获取插件版本
func (p *BasePlugin) Version() string {
return p.version
}
// Description 获取插件描述
func (p *BasePlugin) Description() string {
return p.description
}
// Author 获取插件作者
func (p *BasePlugin) Author() string {
return p.author
}
// IsEnabled 插件是否启用
func (p *BasePlugin) IsEnabled() bool {
return p.enabled
}
// SetEnabled 设置插件启用状态
func (p *BasePlugin) SetEnabled(enabled bool) {
p.enabled = enabled
}
// Init 初始化插件,子类需要重写此方法
func (p *BasePlugin) Init(config map[string]interface{}) error {
return nil
}
// Start 启动插件,子类需要重写此方法
func (p *BasePlugin) Start() error {
return nil
}
// Stop 停止插件,子类需要重写此方法
func (p *BasePlugin) Stop() error {
return nil
}

View File

@@ -0,0 +1,169 @@
package main
import (
"context"
"fmt"
"runtime"
"github.com/darkit/goproxy/examples/plugin"
)
// 这是内置插件的示例实现
// LogPlugin 日志插件
type LogPlugin struct {
name string
version string
description string
author string
enabled bool
level string
}
// 创建内置日志插件
func NewLogPlugin() *LogPlugin {
return &LogPlugin{
name: "InternalLogPlugin",
version: "1.0.0",
description: "内置日志插件,在不支持动态加载插件的平台上使用",
author: "开发者",
enabled: true,
level: "info",
}
}
// 实现Plugin接口
func (p *LogPlugin) Name() string {
return p.name
}
func (p *LogPlugin) Version() string {
return p.version
}
func (p *LogPlugin) Description() string {
return p.description
}
func (p *LogPlugin) Author() string {
return p.author
}
func (p *LogPlugin) IsEnabled() bool {
return p.enabled
}
func (p *LogPlugin) SetEnabled(enabled bool) {
p.enabled = enabled
}
func (p *LogPlugin) Init(ctx context.Context, config map[string]interface{}) error {
if level, ok := config["level"].(string); ok {
p.level = level
}
fmt.Println("内置日志插件已初始化,日志级别:", p.level)
return nil
}
func (p *LogPlugin) Start(ctx context.Context) error {
fmt.Println("内置日志插件已启动")
return nil
}
func (p *LogPlugin) Stop(ctx context.Context) error {
fmt.Println("内置日志插件已停止")
return nil
}
// Log 记录日志
func (p *LogPlugin) Log(message string) {
fmt.Printf("[%s] %s\n", p.level, message)
}
func main() {
// 创建插件管理器
pluginsDir := "./plugins"
pm := plugin.NewPluginManager(pluginsDir)
// 检查当前系统是否支持动态加载插件
if pm.IsDynamicLoadingSupported() {
fmt.Printf("当前系统(%s)支持动态加载插件\n", runtime.GOOS)
} else {
fmt.Printf("当前系统(%s)不支持动态加载插件\n", runtime.GOOS)
// 在不支持动态加载的系统上使用内置插件
logPlugin := NewLogPlugin()
pm.RegisterPlugin(logPlugin)
}
// 加载插件
fmt.Println("正在加载插件...")
if err := pm.LoadPlugins(); err != nil {
fmt.Printf("加载插件失败: %v\n", err)
}
// 配置插件
logPluginConfig := map[string]interface{}{
"level": "debug",
}
if err := pm.SetPluginConfig("InternalLogPlugin", logPluginConfig); err != nil {
fmt.Printf("配置插件失败: %v\n", err)
}
// 初始化插件
ctx := context.Background()
fmt.Println("正在初始化插件...")
if err := pm.InitPlugins(ctx); err != nil {
fmt.Printf("初始化插件失败: %v\n", err)
}
// 启动插件
fmt.Println("正在启动插件...")
if err := pm.StartPlugins(ctx); err != nil {
fmt.Printf("启动插件失败: %v\n", err)
}
// 获取并打印所有插件信息
fmt.Println("\n已加载的插件列表:")
plugins := pm.GetPluginInfos()
for i, info := range plugins {
status := "启用"
if !info.Enabled {
status = "禁用"
}
fmt.Printf("[%d] %s (v%s) - %s [%s]\n作者: %s\n",
i, info.Name, info.Version, info.Description, status, info.Author)
// 打印插件配置
if len(info.Config) > 0 {
fmt.Println("配置:")
for k, v := range info.Config {
fmt.Printf(" %s: %v\n", k, v)
}
}
fmt.Println()
}
// 使用第一个启用的插件(这里只是示例)
if len(plugins) > 0 {
for _, info := range plugins {
if info.Enabled {
fmt.Printf("正在使用插件: %s\n", info.Name)
if p, ok := pm.GetPlugin(info.Name); ok {
// 这里可以根据插件类型执行特定操作
if logPlugin, ok := p.(*LogPlugin); ok {
logPlugin.Log("这是一条测试日志消息")
}
}
break
}
}
}
// 停止插件
fmt.Println("\n正在停止插件...")
if err := pm.StopPlugins(ctx); err != nil {
fmt.Printf("停止插件失败: %v\n", err)
}
fmt.Println("示例结束")
}

410
examples/plugin/plugin.go Normal file
View File

@@ -0,0 +1,410 @@
package plugin
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"plugin"
"runtime"
"sync"
)
// Plugin 插件接口
type Plugin interface {
// Name 插件名称
Name() string
// Version 插件版本
Version() string
// Description 插件描述
Description() string
// Author 插件作者
Author() string
// Init 初始化插件
Init(ctx context.Context, config map[string]interface{}) error
// Start 启动插件
Start(ctx context.Context) error
// Stop 停止插件
Stop(ctx context.Context) error
// IsEnabled 插件是否启用
IsEnabled() bool
// SetEnabled 设置插件启用状态
SetEnabled(enabled bool)
}
// PluginInfo 插件信息
type PluginInfo struct {
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description"`
Author string `json:"author"`
Enabled bool `json:"enabled"`
Config map[string]interface{} `json:"config,omitempty"`
}
// PluginManager 插件管理器
type PluginManager struct {
pluginsDir string
plugins map[string]Plugin
configs map[string]map[string]interface{}
mu sync.RWMutex
dynamicLoadingSupported bool // 是否支持动态加载插件
}
// NewPluginManager 创建插件管理器
func NewPluginManager(pluginsDir string) *PluginManager {
// 检查当前系统是否支持动态加载插件
dynamicLoadingSupported := runtime.GOOS == "linux" || runtime.GOOS == "darwin"
return &PluginManager{
pluginsDir: pluginsDir,
plugins: make(map[string]Plugin),
configs: make(map[string]map[string]interface{}),
dynamicLoadingSupported: dynamicLoadingSupported,
}
}
// IsDynamicLoadingSupported 检查是否支持动态加载插件
func (pm *PluginManager) IsDynamicLoadingSupported() bool {
return pm.dynamicLoadingSupported
}
// LoadPlugins 加载插件
func (pm *PluginManager) LoadPlugins() error {
// 确保插件目录存在
if err := os.MkdirAll(pm.pluginsDir, 0o755); err != nil {
return fmt.Errorf("创建插件目录失败: %v", err)
}
// 加载插件配置
if err := pm.loadPluginConfigs(); err != nil {
return fmt.Errorf("加载插件配置失败: %v", err)
}
// 如果不支持动态加载,则返回
if !pm.dynamicLoadingSupported {
fmt.Printf("警告: 当前系统(%s)不支持动态加载插件,跳过加载\n", runtime.GOOS)
return nil
}
// 遍历插件目录
err := filepath.Walk(pm.pluginsDir, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
// 跳过目录和非.so文件
if info.IsDir() || filepath.Ext(path) != ".so" {
return nil
}
// 加载插件
if err := pm.loadPlugin(path); err != nil {
fmt.Printf("加载插件 %s 失败: %v\n", path, err)
}
return nil
})
return err
}
// loadPluginConfigs 加载插件配置
func (pm *PluginManager) loadPluginConfigs() error {
configPath := filepath.Join(pm.pluginsDir, "plugins.json")
// 如果配置文件不存在,创建一个空的
if _, err := os.Stat(configPath); os.IsNotExist(err) {
file, err := os.Create(configPath)
if err != nil {
return fmt.Errorf("创建插件配置文件失败: %v", err)
}
file.Write([]byte("{}"))
file.Close()
return nil
}
// 读取配置文件
data, err := os.ReadFile(configPath)
if err != nil {
return fmt.Errorf("读取插件配置文件失败: %v", err)
}
// 解析配置
var configs map[string]map[string]interface{}
if err := json.Unmarshal(data, &configs); err != nil {
return fmt.Errorf("解析插件配置文件失败: %v", err)
}
pm.configs = configs
return nil
}
// savePluginConfigs 保存插件配置
func (pm *PluginManager) savePluginConfigs() error {
configPath := filepath.Join(pm.pluginsDir, "plugins.json")
// 创建配置数据
configs := make(map[string]map[string]interface{})
pm.mu.RLock()
for name, plugin := range pm.plugins {
config := pm.configs[name]
if config == nil {
config = make(map[string]interface{})
}
config["enabled"] = plugin.IsEnabled()
configs[name] = config
}
pm.mu.RUnlock()
// 序列化配置
data, err := json.MarshalIndent(configs, "", " ")
if err != nil {
return fmt.Errorf("序列化插件配置失败: %v", err)
}
// 写入文件
if err := os.WriteFile(configPath, data, 0o644); err != nil {
return fmt.Errorf("写入插件配置文件失败: %v", err)
}
return nil
}
// loadPlugin 加载单个插件
func (pm *PluginManager) loadPlugin(path string) error {
// 打开插件文件
p, err := plugin.Open(path)
if err != nil {
return fmt.Errorf("打开插件失败: %v", err)
}
// 查找Plugin变量
symPlugin, err := p.Lookup("Plugin")
if err != nil {
return fmt.Errorf("查找Plugin变量失败: %v", err)
}
// 类型断言
plugin, ok := symPlugin.(Plugin)
if !ok {
return errors.New("插件类型错误")
}
// 检查插件名称是否已存在
pm.mu.Lock()
defer pm.mu.Unlock()
if _, exists := pm.plugins[plugin.Name()]; exists {
return fmt.Errorf("插件 %s 已存在", plugin.Name())
}
// 设置插件启用状态
if config, exists := pm.configs[plugin.Name()]; exists {
if enabled, ok := config["enabled"].(bool); ok {
plugin.SetEnabled(enabled)
} else {
plugin.SetEnabled(true) // 默认启用
}
} else {
plugin.SetEnabled(true) // 默认启用
}
pm.plugins[plugin.Name()] = plugin
return nil
}
// RegisterPlugin 注册内置插件
// 用于在不支持动态加载的平台上注册插件
func (pm *PluginManager) RegisterPlugin(plugin Plugin) error {
pm.mu.Lock()
defer pm.mu.Unlock()
if _, exists := pm.plugins[plugin.Name()]; exists {
return fmt.Errorf("插件 %s 已存在", plugin.Name())
}
// 设置插件启用状态
if config, exists := pm.configs[plugin.Name()]; exists {
if enabled, ok := config["enabled"].(bool); ok {
plugin.SetEnabled(enabled)
} else {
plugin.SetEnabled(true) // 默认启用
}
} else {
plugin.SetEnabled(true) // 默认启用
}
pm.plugins[plugin.Name()] = plugin
return nil
}
// GetPlugin 获取插件
func (pm *PluginManager) GetPlugin(name string) (Plugin, bool) {
pm.mu.RLock()
defer pm.mu.RUnlock()
plugin, exists := pm.plugins[name]
return plugin, exists
}
// GetAllPlugins 获取所有插件
func (pm *PluginManager) GetAllPlugins() []Plugin {
pm.mu.RLock()
defer pm.mu.RUnlock()
plugins := make([]Plugin, 0, len(pm.plugins))
for _, plugin := range pm.plugins {
plugins = append(plugins, plugin)
}
return plugins
}
// GetPluginInfos 获取所有插件信息
func (pm *PluginManager) GetPluginInfos() []PluginInfo {
pm.mu.RLock()
defer pm.mu.RUnlock()
infos := make([]PluginInfo, 0, len(pm.plugins))
for name, plugin := range pm.plugins {
info := PluginInfo{
Name: plugin.Name(),
Version: plugin.Version(),
Description: plugin.Description(),
Author: plugin.Author(),
Enabled: plugin.IsEnabled(),
Config: pm.configs[name],
}
infos = append(infos, info)
}
return infos
}
// EnablePlugin 启用插件
func (pm *PluginManager) EnablePlugin(name string) error {
pm.mu.Lock()
defer pm.mu.Unlock()
plugin, exists := pm.plugins[name]
if !exists {
return fmt.Errorf("插件 %s 不存在", name)
}
plugin.SetEnabled(true)
// 更新配置
if pm.configs[name] == nil {
pm.configs[name] = make(map[string]interface{})
}
pm.configs[name]["enabled"] = true
// 保存配置
return pm.savePluginConfigs()
}
// DisablePlugin 禁用插件
func (pm *PluginManager) DisablePlugin(name string) error {
pm.mu.Lock()
defer pm.mu.Unlock()
plugin, exists := pm.plugins[name]
if !exists {
return fmt.Errorf("插件 %s 不存在", name)
}
plugin.SetEnabled(false)
// 更新配置
if pm.configs[name] == nil {
pm.configs[name] = make(map[string]interface{})
}
pm.configs[name]["enabled"] = false
// 保存配置
return pm.savePluginConfigs()
}
// InitPlugins 初始化所有插件
func (pm *PluginManager) InitPlugins(ctx context.Context) error {
pm.mu.RLock()
defer pm.mu.RUnlock()
for name, plugin := range pm.plugins {
if !plugin.IsEnabled() {
continue
}
config := pm.configs[name]
if err := plugin.Init(ctx, config); err != nil {
return fmt.Errorf("初始化插件 %s 失败: %v", name, err)
}
}
return nil
}
// StartPlugins 启动所有插件
func (pm *PluginManager) StartPlugins(ctx context.Context) error {
pm.mu.RLock()
defer pm.mu.RUnlock()
for name, plugin := range pm.plugins {
if !plugin.IsEnabled() {
continue
}
if err := plugin.Start(ctx); err != nil {
return fmt.Errorf("启动插件 %s 失败: %v", name, err)
}
}
return nil
}
// StopPlugins 停止所有插件
func (pm *PluginManager) StopPlugins(ctx context.Context) error {
pm.mu.RLock()
defer pm.mu.RUnlock()
for name, plugin := range pm.plugins {
if !plugin.IsEnabled() {
continue
}
if err := plugin.Stop(ctx); err != nil {
return fmt.Errorf("停止插件 %s 失败: %v", name, err)
}
}
return nil
}
// SetPluginConfig 设置插件配置
func (pm *PluginManager) SetPluginConfig(name string, config map[string]interface{}) error {
pm.mu.Lock()
defer pm.mu.Unlock()
if _, exists := pm.plugins[name]; !exists {
return fmt.Errorf("插件 %s 不存在", name)
}
pm.configs[name] = config
return pm.savePluginConfigs()
}
// GetPluginConfig 获取插件配置
func (pm *PluginManager) GetPluginConfig(name string) (map[string]interface{}, error) {
pm.mu.RLock()
defer pm.mu.RUnlock()
if _, exists := pm.plugins[name]; !exists {
return nil, fmt.Errorf("插件 %s 不存在", name)
}
config := pm.configs[name]
if config == nil {
config = make(map[string]interface{})
}
return config, nil
}

View File

@@ -0,0 +1,22 @@
#!/bin/bash
# 编译插件
go build -buildmode=plugin -o stats.so ./stats/stats.go
# 检查编译结果
if [ $? -eq 0 ]; then
echo "插件编译成功: stats.so"
else
echo "插件编译失败"
exit 1
fi
go build -buildmode=plugin -o logger.so ./logger/logger.go
# 检查编译结果
if [ $? -eq 0 ]; then
echo "插件编译成功: logger.so"
else
echo "插件编译失败"
exit 1
fi

View File

@@ -0,0 +1,126 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"time"
"github.com/darkit/goproxy/examples/plugin"
)
// LoggerPlugin 日志插件
// 提供文件日志和控制台日志功能
type LoggerPlugin struct {
*plugin.BasePlugin // 嵌入基本插件结构
logFile *os.File // 日志文件
logger *log.Logger // 日志记录器
config map[string]interface{} // 配置
}
// Plugin 导出的插件变量
// 注意变量名必须是Plugin大小写敏感
var Plugin = &LoggerPlugin{
BasePlugin: plugin.NewBasePlugin(
"LoggerPlugin",
"1.0.0",
"简单的日志记录插件",
"开发者",
),
}
// Init 初始化插件
func (p *LoggerPlugin) Init(config map[string]interface{}) error {
p.config = config
// 获取日志文件路径
logPath, ok := config["log_path"].(string)
if !ok {
// 使用默认路径
logPath = "logs"
}
// 确保日志目录存在
if err := os.MkdirAll(logPath, 0o755); err != nil {
return fmt.Errorf("创建日志目录失败: %v", err)
}
// 创建日志文件
logFilePath := filepath.Join(logPath, fmt.Sprintf("app_%s.log", time.Now().Format("2006-01-02")))
logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return fmt.Errorf("打开日志文件失败: %v", err)
}
p.logFile = logFile
p.logger = log.New(logFile, "[LoggerPlugin] ", log.LstdFlags)
p.logger.Println("日志插件初始化完成")
fmt.Println("日志插件初始化完成,日志文件:", logFilePath)
return nil
}
// Start 启动插件
func (p *LoggerPlugin) Start() error {
if p.logger == nil {
return fmt.Errorf("插件未初始化")
}
p.logger.Println("日志插件已启动")
fmt.Println("日志插件已启动")
return nil
}
// Stop 停止插件
func (p *LoggerPlugin) Stop() error {
if p.logger != nil {
p.logger.Println("日志插件正在停止")
}
if p.logFile != nil {
if err := p.logFile.Close(); err != nil {
return fmt.Errorf("关闭日志文件失败: %v", err)
}
}
fmt.Println("日志插件已停止")
return nil
}
// Log 记录日志
func (p *LoggerPlugin) Log(level, message string) {
if p.logger == nil {
fmt.Printf("[%s] %s\n", level, message)
return
}
logMsg := fmt.Sprintf("[%s] %s", level, message)
p.logger.Println(logMsg)
// 如果配置了同时输出到控制台
if consoleOutput, ok := p.config["console_output"].(bool); ok && consoleOutput {
fmt.Println(logMsg)
}
}
// Info 记录信息日志
func (p *LoggerPlugin) Info(message string) {
p.Log("INFO", message)
}
// Warn 记录警告日志
func (p *LoggerPlugin) Warn(message string) {
p.Log("WARN", message)
}
// Error 记录错误日志
func (p *LoggerPlugin) Error(message string) {
p.Log("ERROR", message)
}
// main 函数是必须的,但不会被调用
func main() {
// 不会被执行,仅用于编译插件
}

View File

@@ -0,0 +1,170 @@
package main
import (
"fmt"
"sync"
"time"
"github.com/darkit/goproxy/examples/plugin"
)
// StatsPlugin 统计插件
// 用于收集和记录系统运行时统计数据
type StatsPlugin struct {
*plugin.BasePlugin
stats map[string]int64
startTime time.Time
mu sync.RWMutex
tickerStop chan bool
ticker *time.Ticker
config map[string]interface{}
}
// Plugin 导出的插件变量
var Plugin = &StatsPlugin{
BasePlugin: plugin.NewBasePlugin(
"StatsPlugin",
"1.0.0",
"系统运行时统计插件",
"开发者",
),
stats: make(map[string]int64),
tickerStop: make(chan bool),
}
// Init 初始化插件
func (p *StatsPlugin) Init(config map[string]interface{}) error {
p.config = config
// 初始化统计指标
p.mu.Lock()
p.stats["requests"] = 0
p.stats["errors"] = 0
p.stats["bytes_sent"] = 0
p.stats["bytes_received"] = 0
p.mu.Unlock()
fmt.Println("统计插件初始化完成")
return nil
}
// Start 启动插件
func (p *StatsPlugin) Start() error {
p.startTime = time.Now()
// 启动定时统计任务
interval := 60 * time.Second // 默认60秒
// 从配置中获取统计间隔
if intervalSec, ok := p.config["interval_seconds"].(float64); ok {
interval = time.Duration(intervalSec) * time.Second
}
p.ticker = time.NewTicker(interval)
go func() {
for {
select {
case <-p.ticker.C:
p.logStats()
case <-p.tickerStop:
p.ticker.Stop()
return
}
}
}()
fmt.Println("统计插件已启动")
return nil
}
// Stop 停止插件
func (p *StatsPlugin) Stop() error {
if p.ticker != nil {
p.tickerStop <- true
}
// 输出最终统计信息
p.logStats()
fmt.Println("统计插件已停止")
return nil
}
// logStats 记录当前统计信息
func (p *StatsPlugin) logStats() {
p.mu.RLock()
defer p.mu.RUnlock()
uptime := time.Since(p.startTime).Seconds()
fmt.Printf("===== 系统统计信息 =====\n")
fmt.Printf("运行时间: %.2f 秒\n", uptime)
fmt.Printf("总请求数: %d\n", p.stats["requests"])
fmt.Printf("错误数: %d\n", p.stats["errors"])
fmt.Printf("发送字节: %d\n", p.stats["bytes_sent"])
fmt.Printf("接收字节: %d\n", p.stats["bytes_received"])
if uptime > 0 && p.stats["requests"] > 0 {
fmt.Printf("平均请求/秒: %.2f\n", float64(p.stats["requests"])/uptime)
fmt.Printf("错误率: %.2f%%\n", float64(p.stats["errors"])*100/float64(p.stats["requests"]))
}
fmt.Printf("=======================\n")
}
// IncrementStat 增加统计值
func (p *StatsPlugin) IncrementStat(name string, value int64) {
p.mu.Lock()
defer p.mu.Unlock()
if _, exists := p.stats[name]; exists {
p.stats[name] += value
} else {
p.stats[name] = value
}
}
// GetStat 获取统计值
func (p *StatsPlugin) GetStat(name string) int64 {
p.mu.RLock()
defer p.mu.RUnlock()
if value, exists := p.stats[name]; exists {
return value
}
return 0
}
// RecordRequest 记录请求
func (p *StatsPlugin) RecordRequest(bytesReceived, bytesSent int64, isError bool) {
p.IncrementStat("requests", 1)
p.IncrementStat("bytes_received", bytesReceived)
p.IncrementStat("bytes_sent", bytesSent)
if isError {
p.IncrementStat("errors", 1)
}
}
// GetAllStats 获取所有统计数据
func (p *StatsPlugin) GetAllStats() map[string]int64 {
p.mu.RLock()
defer p.mu.RUnlock()
// 创建一个副本
statsCopy := make(map[string]int64, len(p.stats))
for k, v := range p.stats {
statsCopy[k] = v
}
// 添加运行时间
statsCopy["uptime_seconds"] = int64(time.Since(p.startTime).Seconds())
return statsCopy
}
// main 函数是必须的,但不会被调用
func main() {
// 不会被执行,仅用于编译插件
}

View File

@@ -0,0 +1,121 @@
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
"github.com/darkit/goproxy/examples/rewriter/rewriter"
)
// RewriteReverseProxy 重写反向代理
// 在请求发送到后端服务器前重写URL
type RewriteReverseProxy struct {
// 后端服务器地址
Target *url.URL
// URL重写器
Rewriter *rewriter.Rewriter
// 反向代理
Proxy *httputil.ReverseProxy
}
// NewRewriteReverseProxy 创建重写反向代理
func NewRewriteReverseProxy(target string, rewriteRulesFile string) (*RewriteReverseProxy, error) {
// 解析目标URL
targetURL, err := url.Parse(target)
if err != nil {
return nil, err
}
// 创建重写器
rw := rewriter.NewRewriter()
// 加载重写规则
if rewriteRulesFile != "" {
if err := rw.LoadRulesFromFile(rewriteRulesFile); err != nil {
return nil, err
}
}
// 创建反向代理
proxy := httputil.NewSingleHostReverseProxy(targetURL)
// 修改默认的Director函数添加URL重写逻辑
defaultDirector := proxy.Director
proxy.Director = func(req *http.Request) {
// 先执行默认的Director函数
defaultDirector(req)
// 然后执行URL重写
rw.Rewrite(req)
// 记录重写后的URL
log.Printf("请求重写: %s -> %s", req.URL.Path, req.URL.String())
}
// 修改响应处理器,重写响应头
proxy.ModifyResponse = func(resp *http.Response) error {
// 重写响应
rw.RewriteResponse(resp, targetURL.Host)
return nil
}
return &RewriteReverseProxy{
Target: targetURL,
Rewriter: rw,
Proxy: proxy,
}, nil
}
// ServeHTTP 实现http.Handler接口
func (rrp *RewriteReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Printf("收到请求: %s %s", r.Method, r.URL.Path)
rrp.Proxy.ServeHTTP(w, r)
}
// 中间件:将重写器应用到处理链中
func RewriteMiddleware(rw *rewriter.Rewriter, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 重写URL
originalPath := r.URL.Path
rw.Rewrite(r)
if originalPath != r.URL.Path {
log.Printf("URL重写: %s -> %s", originalPath, r.URL.Path)
}
// 继续处理链
next.ServeHTTP(w, r)
})
}
func main1() {
// 创建重写器
rw := rewriter.NewRewriter()
// 加载重写规则
if err := rw.LoadRulesFromFile("rules.json"); err != nil {
log.Fatalf("加载重写规则失败: %v", err)
}
// 创建反向代理
proxy, err := NewRewriteReverseProxy("http://localhost:8081", "rules.json")
if err != nil {
log.Fatalf("创建反向代理失败: %v", err)
}
// 创建静态文件服务器(模拟普通Web服务器)
fileServer := http.FileServer(http.Dir("./static"))
// 使用中间件包装文件服务器
rewrittenFileServer := RewriteMiddleware(rw, fileServer)
// 设置处理函数
http.Handle("/proxy/", http.StripPrefix("/proxy", proxy))
http.Handle("/", rewrittenFileServer)
// 启动服务器
log.Println("服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}

88
examples/rewriter/main.go Normal file
View File

@@ -0,0 +1,88 @@
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"github.com/darkit/goproxy/examples/rewriter/rewriter"
)
func main2() {
// 创建URL重写器
rw := rewriter.NewRewriter()
// 添加一些规则
rw.AddRule("/api/v1/", "/api/v2/", false)
rw.AddRuleWithDescription("/old/(.*)/page", "/new/$1/page", true, "旧页面重定向")
// 从文件加载规则
exampleDir, _ := os.Getwd()
jsonRulesPath := filepath.Join(exampleDir, "rules.json")
textRulesPath := filepath.Join(exampleDir, "rules.txt")
// 先加载JSON格式规则
fmt.Println("从JSON文件加载规则...")
if err := rw.LoadRulesFromFile(jsonRulesPath); err != nil {
fmt.Printf("从JSON文件加载规则失败: %v\n", err)
}
// 然后加载文本格式规则
fmt.Println("从文本文件加载规则...")
if err := rw.LoadRulesFromFile(textRulesPath); err != nil {
fmt.Printf("从文本文件加载规则失败: %v\n", err)
}
// 打印所有规则
fmt.Println("\n当前规则列表:")
for i, rule := range rw.GetRules() {
status := "启用"
if !rule.Enabled {
status = "禁用"
}
fmt.Printf("[%d] %s -> %s [%s] (%s)\n",
i, rule.Pattern, rule.Replacement,
map[bool]string{true: "正则", false: "前缀"}[rule.UseRegex],
status)
}
// 测试重写
fmt.Println("\n测试URL重写:")
testURLs := []string{
"/api/v1/users",
"/old/profile/page",
"/legacy-files/document.pdf",
"/en/about/company",
}
for _, url := range testURLs {
// 创建测试请求
req := httptest.NewRequest("GET", "http://example.com"+url, nil)
// 重写URL
fmt.Printf("原始URL: %s\n", req.URL.Path)
rw.Rewrite(req)
fmt.Printf("重写后: %s\n\n", req.URL.Path)
}
// 测试响应重写
fmt.Println("测试响应重写:")
resp := &http.Response{
Header: http.Header{},
}
resp.Header.Set("Location", "http://backend.example.com/old/profile/page")
fmt.Printf("原始Location: %s\n", resp.Header.Get("Location"))
rw.RewriteResponse(resp, "backend.example.com")
fmt.Printf("重写后Location: %s\n", resp.Header.Get("Location"))
// 保存规则到新文件
newRulesPath := filepath.Join(exampleDir, "new_rules.json")
fmt.Printf("\n保存规则到文件: %s\n", newRulesPath)
if err := rw.SaveRulesToFile(newRulesPath); err != nil {
fmt.Printf("保存规则失败: %v\n", err)
} else {
fmt.Println("规则已保存")
}
}

View File

@@ -0,0 +1,285 @@
package rewriter
import (
"bufio"
"encoding/json"
"fmt"
"net/http"
"os"
"regexp"
"strings"
)
// Rewriter URL重写器
// 用于在反向代理中重写请求URL
type Rewriter struct {
// 重写规则列表
rules []*RewriteRule
}
// RewriteRule 重写规则
type RewriteRule struct {
// 匹配模式
Pattern string `json:"pattern"`
// 替换模式
Replacement string `json:"replacement"`
// 是否使用正则表达式
UseRegex bool `json:"use_regex"`
// 编译后的正则表达式
regex *regexp.Regexp `json:"-"`
// 规则描述
Description string `json:"description,omitempty"`
// 规则启用状态
Enabled bool `json:"enabled,omitempty"`
}
// NewRewriter 创建URL重写器
func NewRewriter() *Rewriter {
return &Rewriter{
rules: make([]*RewriteRule, 0),
}
}
// AddRule 添加重写规则
func (r *Rewriter) AddRule(pattern, replacement string, useRegex bool) error {
rule := &RewriteRule{
Pattern: pattern,
Replacement: replacement,
UseRegex: useRegex,
Enabled: true,
}
if useRegex {
regex, err := regexp.Compile(pattern)
if err != nil {
return err
}
rule.regex = regex
}
r.rules = append(r.rules, rule)
return nil
}
// AddRuleWithDescription 添加带描述的重写规则
func (r *Rewriter) AddRuleWithDescription(pattern, replacement string, useRegex bool, description string) error {
rule := &RewriteRule{
Pattern: pattern,
Replacement: replacement,
UseRegex: useRegex,
Description: description,
Enabled: true,
}
if useRegex {
regex, err := regexp.Compile(pattern)
if err != nil {
return err
}
rule.regex = regex
}
r.rules = append(r.rules, rule)
return nil
}
// Rewrite 重写URL
func (r *Rewriter) Rewrite(req *http.Request) {
path := req.URL.Path
for _, rule := range r.rules {
if !rule.Enabled {
continue
}
if rule.UseRegex {
if rule.regex.MatchString(path) {
req.URL.Path = rule.regex.ReplaceAllString(path, rule.Replacement)
break
}
} else {
if strings.HasPrefix(path, rule.Pattern) {
req.URL.Path = strings.Replace(path, rule.Pattern, rule.Replacement, 1)
break
}
}
}
}
// RewriteResponse 重写响应
// 主要用于处理响应中的Location头和内容中的URL
func (r *Rewriter) RewriteResponse(resp *http.Response, originHost string) {
// 处理重定向头
location := resp.Header.Get("Location")
if location != "" {
// 将后端服务器的域名替换成代理服务器的域名
for _, rule := range r.rules {
if !rule.Enabled {
continue
}
if rule.UseRegex && rule.regex != nil {
if rule.regex.MatchString(location) {
newLocation := rule.regex.ReplaceAllString(location, rule.Replacement)
resp.Header.Set("Location", newLocation)
break
}
}
}
}
}
// LoadRulesFromFile 从文件加载重写规则
func (r *Rewriter) LoadRulesFromFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return fmt.Errorf("打开文件失败: %v", err)
}
defer file.Close()
// 检查文件扩展名,决定使用何种方式解析
if strings.HasSuffix(filename, ".json") {
return r.loadRulesFromJSON(file)
} else {
return r.loadRulesFromText(file)
}
}
// loadRulesFromJSON 从JSON文件加载规则
func (r *Rewriter) loadRulesFromJSON(file *os.File) error {
var rules []*RewriteRule
decoder := json.NewDecoder(file)
if err := decoder.Decode(&rules); err != nil {
return fmt.Errorf("解析JSON失败: %v", err)
}
// 编译正则表达式
for _, rule := range rules {
if rule.UseRegex {
regex, err := regexp.Compile(rule.Pattern)
if err != nil {
return fmt.Errorf("编译正则表达式'%s'失败: %v", rule.Pattern, err)
}
rule.regex = regex
}
r.rules = append(r.rules, rule)
}
return nil
}
// loadRulesFromText 从文本文件加载规则
// 格式: pattern replacement [regex] [#description]
func (r *Rewriter) loadRulesFromText(file *os.File) error {
scanner := bufio.NewScanner(file)
lineNum := 0
for scanner.Scan() {
lineNum++
line := strings.TrimSpace(scanner.Text())
// 跳过空行和注释
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.Fields(line)
if len(parts) < 2 {
return fmt.Errorf("第%d行格式错误: %s", lineNum, line)
}
pattern := parts[0]
replacement := parts[1]
useRegex := false
description := ""
// 检查是否有额外选项
for i := 2; i < len(parts); i++ {
if parts[i] == "regex" {
useRegex = true
} else if strings.HasPrefix(parts[i], "#") {
// 获取描述信息
description = strings.Join(parts[i:], " ")
description = strings.TrimPrefix(description, "#")
description = strings.TrimSpace(description)
break
}
}
if err := r.AddRuleWithDescription(pattern, replacement, useRegex, description); err != nil {
return fmt.Errorf("第%d行添加规则失败: %v", lineNum, err)
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("读取文件失败: %v", err)
}
return nil
}
// GetRules 获取所有规则
func (r *Rewriter) GetRules() []*RewriteRule {
return r.rules
}
// EnableRule 启用规则
func (r *Rewriter) EnableRule(index int) error {
if index < 0 || index >= len(r.rules) {
return fmt.Errorf("规则索引越界: %d", index)
}
r.rules[index].Enabled = true
return nil
}
// DisableRule 禁用规则
func (r *Rewriter) DisableRule(index int) error {
if index < 0 || index >= len(r.rules) {
return fmt.Errorf("规则索引越界: %d", index)
}
r.rules[index].Enabled = false
return nil
}
// RemoveRule 删除规则
func (r *Rewriter) RemoveRule(index int) error {
if index < 0 || index >= len(r.rules) {
return fmt.Errorf("规则索引越界: %d", index)
}
r.rules = append(r.rules[:index], r.rules[index+1:]...)
return nil
}
// SaveRulesToFile 将规则保存到文件
func (r *Rewriter) SaveRulesToFile(filename string) error {
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("创建文件失败: %v", err)
}
defer file.Close()
// 根据文件扩展名决定保存格式
if strings.HasSuffix(filename, ".json") {
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
return encoder.Encode(r.rules)
} else {
writer := bufio.NewWriter(file)
for _, rule := range r.rules {
line := rule.Pattern + " " + rule.Replacement
if rule.UseRegex {
line += " regex"
}
if rule.Description != "" {
line += " # " + rule.Description
}
if !rule.Enabled {
line = "# " + line + " (disabled)"
}
if _, err := writer.WriteString(line + "\n"); err != nil {
return fmt.Errorf("写入文件失败: %v", err)
}
}
return writer.Flush()
}
}

View File

@@ -0,0 +1,30 @@
[
{
"pattern": "/api/v1/",
"replacement": "/api/v2/",
"use_regex": false,
"description": "将API v1请求重定向到v2",
"enabled": true
},
{
"pattern": "/old/(.*)/page",
"replacement": "/new/$1/page",
"use_regex": true,
"description": "旧页面格式重定向到新格式",
"enabled": true
},
{
"pattern": "/legacy-files/",
"replacement": "/files/",
"use_regex": false,
"description": "旧文件路径重定向",
"enabled": false
},
{
"pattern": "^/(en|zh|ja)/(.*)",
"replacement": "/$2?lang=$1",
"use_regex": true,
"description": "将语言路径转换为查询参数",
"enabled": true
}
]

View File

@@ -0,0 +1,7 @@
# URL重写规则示例
# 格式: pattern replacement [regex] [#description]
/api/v1/ /api/v2/ # 将API v1请求重定向到v2
/old/(.*)/page /new/$1/page regex # 旧页面格式重定向到新格式
# /legacy-files/ /files/ # 旧文件路径重定向 (已禁用)
^/(en|zh|ja)/(.*) /$2?lang=$1 regex # 将语言路径转换为查询参数

View File

@@ -0,0 +1,259 @@
package main
import (
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"strconv"
"github.com/darkit/goproxy/examples/rewriter/rewriter"
)
// 全局重写器实例
var rw *rewriter.Rewriter
// 规则配置文件路径
var rulesFile = "rules.json"
// AdminHandler Web管理页面处理器
func AdminHandler(w http.ResponseWriter, r *http.Request) {
// 定义模板
tmpl := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>URL重写规则管理</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #f2f2f2; }
.enabled { color: green; }
.disabled { color: red; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; }
input[type="text"], textarea { width: 100%; padding: 8px; box-sizing: border-box; }
input[type="checkbox"] { margin-right: 5px; }
button { padding: 10px 15px; background-color: #4CAF50; color: white; border: none; cursor: pointer; }
button:hover { background-color: #45a049; }
.action-btn { padding: 5px 10px; margin-right: 5px; }
.delete-btn { background-color: #f44336; }
.edit-btn { background-color: #2196F3; }
</style>
</head>
<body>
<h1>URL重写规则管理</h1>
<h2>当前规则</h2>
<table>
<tr>
<th>索引</th>
<th>匹配模式</th>
<th>替换模式</th>
<th>类型</th>
<th>描述</th>
<th>状态</th>
<th>操作</th>
</tr>
{{range $i, $rule := .Rules}}
<tr>
<td>{{$i}}</td>
<td>{{$rule.Pattern}}</td>
<td>{{$rule.Replacement}}</td>
<td>{{if $rule.UseRegex}}正则表达式{{else}}前缀匹配{{end}}</td>
<td>{{$rule.Description}}</td>
<td class="{{if $rule.Enabled}}enabled{{else}}disabled{{end}}">
{{if $rule.Enabled}}启用{{else}}禁用{{end}}
</td>
<td>
{{if $rule.Enabled}}
<a href="/disable/{{$i}}"><button class="action-btn">禁用</button></a>
{{else}}
<a href="/enable/{{$i}}"><button class="action-btn">启用</button></a>
{{end}}
<a href="/remove/{{$i}}"><button class="action-btn delete-btn">删除</button></a>
</td>
</tr>
{{end}}
</table>
<h2>添加新规则</h2>
<form action="/add" method="post">
<div class="form-group">
<label for="pattern">匹配模式:</label>
<input type="text" id="pattern" name="pattern" required>
</div>
<div class="form-group">
<label for="replacement">替换模式:</label>
<input type="text" id="replacement" name="replacement" required>
</div>
<div class="form-group">
<label for="regex">
<input type="checkbox" id="regex" name="regex">
使用正则表达式
</label>
</div>
<div class="form-group">
<label for="description">描述:</label>
<textarea id="description" name="description" rows="3"></textarea>
</div>
<button type="submit">添加规则</button>
</form>
<h2>保存配置</h2>
<form action="/save" method="post">
<button type="submit">保存规则配置</button>
</form>
</body>
</html>
`
// 解析模板
t, err := template.New("admin").Parse(tmpl)
if err != nil {
http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
return
}
// 渲染模板
data := struct {
Rules []*rewriter.RewriteRule
}{
Rules: rw.GetRules(),
}
if err := t.Execute(w, data); err != nil {
http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
}
}
// AddRuleHandler 添加规则处理器
func AddRuleHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
r.ParseForm()
pattern := r.Form.Get("pattern")
replacement := r.Form.Get("replacement")
useRegex := r.Form.Get("regex") == "on"
description := r.Form.Get("description")
if pattern == "" || replacement == "" {
http.Error(w, "匹配模式和替换模式不能为空", http.StatusBadRequest)
return
}
if err := rw.AddRuleWithDescription(pattern, replacement, useRegex, description); err != nil {
http.Error(w, fmt.Sprintf("添加规则失败: %v", err), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// EnableRuleHandler 启用规则处理器
func EnableRuleHandler(w http.ResponseWriter, r *http.Request) {
// 解析URL中的规则索引
indexStr := r.URL.Path[len("/enable/"):]
index, err := strconv.Atoi(indexStr)
if err != nil {
http.Error(w, "无效的规则索引", http.StatusBadRequest)
return
}
if err := rw.EnableRule(index); err != nil {
http.Error(w, fmt.Sprintf("启用规则失败: %v", err), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// DisableRuleHandler 禁用规则处理器
func DisableRuleHandler(w http.ResponseWriter, r *http.Request) {
// 解析URL中的规则索引
indexStr := r.URL.Path[len("/disable/"):]
index, err := strconv.Atoi(indexStr)
if err != nil {
http.Error(w, "无效的规则索引", http.StatusBadRequest)
return
}
if err := rw.DisableRule(index); err != nil {
http.Error(w, fmt.Sprintf("禁用规则失败: %v", err), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// RemoveRuleHandler 删除规则处理器
func RemoveRuleHandler(w http.ResponseWriter, r *http.Request) {
// 解析URL中的规则索引
indexStr := r.URL.Path[len("/remove/"):]
index, err := strconv.Atoi(indexStr)
if err != nil {
http.Error(w, "无效的规则索引", http.StatusBadRequest)
return
}
if err := rw.RemoveRule(index); err != nil {
http.Error(w, fmt.Sprintf("删除规则失败: %v", err), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// SaveRulesHandler 保存规则处理器
func SaveRulesHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
if err := rw.SaveRulesToFile(rulesFile); err != nil {
http.Error(w, fmt.Sprintf("保存规则失败: %v", err), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// APIHandler API处理器返回JSON格式的规则列表
func APIHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
rules := rw.GetRules()
json.NewEncoder(w).Encode(rules)
}
func main() {
// 创建重写器
rw = rewriter.NewRewriter()
// 尝试从文件加载规则
if err := rw.LoadRulesFromFile(rulesFile); err != nil {
log.Printf("加载规则失败: %v, 将使用空规则集", err)
}
// 注册处理器
http.HandleFunc("/", AdminHandler)
http.HandleFunc("/add", AddRuleHandler)
http.HandleFunc("/enable/", EnableRuleHandler)
http.HandleFunc("/disable/", DisableRuleHandler)
http.HandleFunc("/remove/", RemoveRuleHandler)
http.HandleFunc("/save", SaveRulesHandler)
http.HandleFunc("/api/rules", APIHandler)
// 启动服务器
log.Println("管理界面启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}