feat: add static file bypass auth + graph.html API_BASE fix

- Add /static/* route bypass in auth middleware
- Localize d3.min.js (was CDN blocked)
- Fix graph.html API_BASE to server IP for remote access
- Add error logging in graph load function
- Deploy binary at /usr/local/bin/zhiyid (13.5MB, correct cmd/zhiyid path)
This commit is contained in:
xiaowei 2026-05-29 21:59:41 +08:00
parent 4a67916cb5
commit 2e8ca614fd
7 changed files with 70 additions and 14 deletions

View File

@ -6,6 +6,7 @@ import (
"log"
"net/http"
"os"
"strings"
"sync"
"time"
)
@ -128,8 +129,8 @@ func Auth(next http.Handler) http.Handler {
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// /health 和 /metrics 不需要认证
if r.URL.Path == "/health" || r.URL.Path == "/metrics" {
// /health, /metrics, /static/* 不需要认证
if r.URL.Path == "/health" || r.URL.Path == "/metrics" || strings.HasPrefix(r.URL.Path, "/static/") {
next.ServeHTTP(w, r)
return
}

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"net/http"
"strings"
"unicode"
"github.com/xiaoxue/memoryweave/internal/governance"
)
@ -71,14 +72,18 @@ func (ga *GraphAPI) Navigate(w http.ResponseWriter, r *http.Request) {
respond(w, 200, map[string]interface{}{"paths": paths, "entity": req.Entity, "count": len(paths)})
}
// normalizeEntity 规整实体名:去特殊字符 + n_前缀
// normalizeEntity 规整实体名:去特殊字符 + n_前缀,保留中文和 Unicode 字符
func normalizeEntity(entity string) string {
if strings.HasPrefix(entity, "n_") {
entity = entity[2:]
}
// 过滤特殊字符
// 过滤特殊字符,保留 Unicode 字母、数字、下划线、中横线
clean := strings.Map(func(r rune) rune {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '-' {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '-' || r == ' ' {
return r
}
// 保留中文和其他 Unicode 字母(如韩文、日文等)
if unicode.IsLetter(r) {
return r
}
return '_'
@ -87,7 +92,7 @@ func normalizeEntity(entity string) string {
for strings.Contains(clean, "__") {
clean = strings.ReplaceAll(clean, "__", "_")
}
clean = strings.Trim(clean, "_")
clean = strings.Trim(clean, "_ ")
if clean == "" {
return "n_unknown"
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"strings"
"time"
"unicode"
)
// AutoGraphUpdater 自动维护知识图谱
@ -76,12 +77,19 @@ type DistillInput struct {
}
func entityID(name string) string {
// 保留 Unicode 字母、数字、下划线、中横线、空格(转下划线)
clean := strings.Map(func(r rune) rune {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '-' {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '-' || r == ' ' {
return r
}
// 保留 Unicode 字母(如中文)
if unicode.IsLetter(r) {
return r
}
return '_' // 特殊字符→下划线
}, strings.ToLower(strings.ReplaceAll(strings.TrimSpace(name), " ", "_")))
}, strings.ToLower(strings.TrimSpace(name)))
// 空格转下划线
clean = strings.ReplaceAll(clean, " ", "_")
// 合并连续下划线
for strings.Contains(clean, "__") {
clean = strings.ReplaceAll(clean, "__", "_")
@ -89,12 +97,16 @@ func entityID(name string) string {
return "n_" + strings.Trim(clean, "_")
}
// cleanEntityName 清洗实体名用于展示(去掉特殊字符,限制长度
// cleanEntityName 清洗实体名用于展示(保留 Unicode 字符
func cleanEntityName(raw string) string {
clean := strings.Map(func(r rune) rune {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == ' ' || r == '-' || r == '_' {
return r
}
// 保留 Unicode 字母(中文、韩文、日文等)
if unicode.IsLetter(r) || unicode.IsDigit(r) {
return r
}
return ' '
}, strings.TrimSpace(raw))
clean = strings.Join(strings.Fields(clean), " ")

View File

@ -240,7 +240,35 @@ func containsRelation(rel, substr string) bool {
return rel == substr
}
// GetGraph 内存图谱的导出(空实现)
// GetGraph 内存图谱的导出
func (g *InMemoryGraph) GetGraph(namespace string) ([]map[string]interface{}, []map[string]interface{}) {
return []map[string]interface{}{}, []map[string]interface{}{}
g.mu.RLock()
defer g.mu.RUnlock()
var nodes []map[string]interface{}
var edges []map[string]interface{}
// 导出匹配 namespace 的节点
for _, n := range g.nodes {
if namespace == "" || n.Namespace == namespace {
nodes = append(nodes, map[string]interface{}{
"id": n.ID,
"name": n.Name,
"type": n.Type,
"namespace": n.Namespace,
})
}
}
// 导出匹配 namespace 的边
for _, e := range g.edges {
if namespace == "" || e.Namespace == namespace {
edges = append(edges, map[string]interface{}{
"id": e.ID,
"source": e.Source,
"target": e.Target,
"relation": e.Relation,
"weight": e.Weight,
"namespace": e.Namespace,
})
}
}
return nodes, edges
}

2
go/static/d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>织忆 MemoryWeave — 知识图谱可视化</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="/static/d3.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
@ -327,7 +327,7 @@
event: '#f85149'
};
const API_BASE = 'http://localhost:7821';
const API_BASE = 'http://100.65.23.30:7821';
const API_KEY = 'zhiyi-dev-key-2026';
let nodes = [], links = [];
let simulation, svg, g, linkGroup, nodeGroup, labelGroup;
@ -354,7 +354,7 @@
const resp = await fetch(url, {
headers: { 'X-API-Key': API_KEY }
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
if (!resp.ok) { const t = await resp.text(); console.error('API error:', resp.status, t.substring(0, 500)); throw new Error(`HTTP ${resp.status}`); }
const data = await resp.json();
nodes = data.nodes || [];
@ -369,6 +369,12 @@
loading.style.display = 'none';
error.style.display = 'block';
error.textContent = `加载失败: ${e.message}`;
console.error('Graph load error:', e);
// Show response status if available
if (typeof resp !== 'undefined' && resp) {
console.log('Response status:', resp.status);
resp.text().then(t => console.log('Response body:', t.substring(0, 500)));
}
}
}

2
go/static/static/d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long