Update InfoGenie

This commit is contained in:
2026-04-03 21:18:29 +08:00
parent 6b3fcc1791
commit dbd72610c1
62 changed files with 3192 additions and 255 deletions

View File

@@ -1,22 +1,22 @@
APP_ENV=development
APP_PORT=5002
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=infogenie-test
DB_USER=infogenie-test
DB_PASSWORD=
JWT_SECRET=
JWT_EXPIRE_DAYS=7
MAIL_HOST=
MAIL_PORT=465
MAIL_USERNAME=
MAIL_PASSWORD=
AUTH_CENTER_API_URL=https://auth.api.shumengya.top
AUTH_CENTER_ADMIN_TOKEN=
INFOGENIE_SITE_ADMIN_TOKEN=
# REDIS_ENABLED=false
# REDIS_ADDR=127.0.0.1:6379
# REDIS_PASSWORD=
# REDIS_DB=10
# REDIS_KEY_PREFIX=infogenie:go:v1:
# REDIS_SITE_TTL=60
APP_ENV=development
APP_PORT=5002
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=infogenie-test
DB_USER=infogenie-test
DB_PASSWORD=
JWT_SECRET=
JWT_EXPIRE_DAYS=7
MAIL_HOST=
MAIL_PORT=465
MAIL_USERNAME=
MAIL_PASSWORD=
AUTH_CENTER_API_URL=https://auth.api.shumengya.top
AUTH_CENTER_ADMIN_TOKEN=
INFOGENIE_SITE_ADMIN_TOKEN=
# REDIS_ENABLED=false
# REDIS_ADDR=127.0.0.1:6379
# REDIS_PASSWORD=
# REDIS_DB=10
# REDIS_KEY_PREFIX=infogenie:go:v1:
# REDIS_SITE_TTL=60

View File

@@ -13,7 +13,6 @@ RUN apk --no-cache add ca-certificates tzdata
ENV TZ=Asia/Shanghai
WORKDIR /app
COPY --from=builder /out/server .
COPY ai_config.json ./
EXPOSE 5002
ENV APP_ENV=production
ENV APP_PORT=5002

View File

@@ -1,119 +1,119 @@
package cache
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
"infogenie-backend/config"
)
// Key suffixes完整 key = KeyPrefix + suffix
const (
KeySite60sDisabled = "site:60s-disabled"
KeySite60sSource = "site:60s-source"
KeySiteAIModelDisabled = "site:ai-model-disabled"
)
func KeySiteFeatureClicks(section string) string {
return "site:feature-clicks:" + section
}
var (
rdb *redis.Client
keyPrefix string
siteCacheTTL time.Duration
redisOn bool
)
// Init 在 REDIS_ENABLED 时连接并 Ping未启用时为空操作
func Init(rc config.RedisConfig) error {
if !rc.Enabled {
redisOn = false
rdb = nil
return nil
}
redisOn = true
keyPrefix = rc.KeyPrefix
siteCacheTTL = rc.SiteTTL
if siteCacheTTL <= 0 {
siteCacheTTL = 60 * time.Second
}
rdb = redis.NewClient(&redis.Options{
Addr: rc.Addr,
Password: rc.Password,
DB: rc.DB,
})
if err := rdb.Ping(context.Background()).Err(); err != nil {
return fmt.Errorf("redis: %w", err)
}
return nil
}
// Enabled 表示已初始化且客户端可用
func Enabled() bool {
return redisOn && rdb != nil
}
func fullKey(suffix string) string {
return keyPrefix + suffix
}
// GetJSON 命中返回 true未启用或未命中返回 falseerr 仅表示 Redis/JSON 异常)
func GetJSON(ctx context.Context, suffix string, dest interface{}) (bool, error) {
if !Enabled() {
return false, nil
}
val, err := rdb.Get(ctx, fullKey(suffix)).Bytes()
if err == redis.Nil {
return false, nil
}
if err != nil {
return false, err
}
if err := json.Unmarshal(val, dest); err != nil {
return false, err
}
return true, nil
}
// SetJSON ttl<=0 时使用配置的 SiteTTL
func SetJSON(ctx context.Context, suffix string, v interface{}, ttl time.Duration) error {
if !Enabled() {
return nil
}
b, err := json.Marshal(v)
if err != nil {
return err
}
if ttl <= 0 {
ttl = siteCacheTTL
}
return rdb.Set(ctx, fullKey(suffix), b, ttl).Err()
}
// Delete 删除若干后缀对应的 key失败仅打日志
func Delete(ctx context.Context, suffixes ...string) {
if !Enabled() || len(suffixes) == 0 {
return
}
keys := make([]string, 0, len(suffixes))
for _, s := range suffixes {
keys = append(keys, fullKey(s))
}
if err := rdb.Del(ctx, keys...).Err(); err != nil {
log.Printf("redis DEL 失败: %v keys=%v", err, keys)
}
}
// Ping 未启用时返回 nil
func Ping(ctx context.Context) error {
if !Enabled() {
return nil
}
return rdb.Ping(ctx).Err()
}
package cache
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
"infogenie-backend/config"
)
// Key suffixes完整 key = KeyPrefix + suffix
const (
KeySite60sDisabled = "site:60s-disabled"
KeySite60sSource = "site:60s-source"
KeySiteAIModelDisabled = "site:ai-model-disabled"
)
func KeySiteFeatureClicks(section string) string {
return "site:feature-clicks:" + section
}
var (
rdb *redis.Client
keyPrefix string
siteCacheTTL time.Duration
redisOn bool
)
// Init 在 REDIS_ENABLED 时连接并 Ping未启用时为空操作
func Init(rc config.RedisConfig) error {
if !rc.Enabled {
redisOn = false
rdb = nil
return nil
}
redisOn = true
keyPrefix = rc.KeyPrefix
siteCacheTTL = rc.SiteTTL
if siteCacheTTL <= 0 {
siteCacheTTL = 60 * time.Second
}
rdb = redis.NewClient(&redis.Options{
Addr: rc.Addr,
Password: rc.Password,
DB: rc.DB,
})
if err := rdb.Ping(context.Background()).Err(); err != nil {
return fmt.Errorf("redis: %w", err)
}
return nil
}
// Enabled 表示已初始化且客户端可用
func Enabled() bool {
return redisOn && rdb != nil
}
func fullKey(suffix string) string {
return keyPrefix + suffix
}
// GetJSON 命中返回 true未启用或未命中返回 falseerr 仅表示 Redis/JSON 异常)
func GetJSON(ctx context.Context, suffix string, dest interface{}) (bool, error) {
if !Enabled() {
return false, nil
}
val, err := rdb.Get(ctx, fullKey(suffix)).Bytes()
if err == redis.Nil {
return false, nil
}
if err != nil {
return false, err
}
if err := json.Unmarshal(val, dest); err != nil {
return false, err
}
return true, nil
}
// SetJSON ttl<=0 时使用配置的 SiteTTL
func SetJSON(ctx context.Context, suffix string, v interface{}, ttl time.Duration) error {
if !Enabled() {
return nil
}
b, err := json.Marshal(v)
if err != nil {
return err
}
if ttl <= 0 {
ttl = siteCacheTTL
}
return rdb.Set(ctx, fullKey(suffix), b, ttl).Err()
}
// Delete 删除若干后缀对应的 key失败仅打日志
func Delete(ctx context.Context, suffixes ...string) {
if !Enabled() || len(suffixes) == 0 {
return
}
keys := make([]string, 0, len(suffixes))
for _, s := range suffixes {
keys = append(keys, fullKey(s))
}
if err := rdb.Del(ctx, keys...).Err(); err != nil {
log.Printf("redis DEL 失败: %v keys=%v", err, keys)
}
}
// Ping 未启用时返回 nil
func Ping(ctx context.Context) error {
if !Enabled() {
return nil
}
return rdb.Ping(ctx).Err()
}

View File

@@ -1,84 +1,84 @@
package main
import (
"context"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/redis/go-redis/v9"
)
// 用法: go run . <host:port> [password]
// 例: go run . 10.1.1.100:6379
func main() {
addr := "10.1.1.100:6379"
pass := os.Getenv("REDIS_PASSWORD")
if len(os.Args) >= 2 {
addr = os.Args[1]
}
if len(os.Args) >= 3 {
pass = os.Args[2]
}
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
defer cancel()
r := redis.NewClient(&redis.Options{Addr: addr, Password: pass})
defer r.Close()
if err := r.Ping(ctx).Err(); err != nil {
fmt.Fprintf(os.Stderr, "连接失败 %s: %v\n", addr, err)
os.Exit(1)
}
s, err := r.Info(ctx, "keyspace").Result()
if err != nil {
fmt.Fprintf(os.Stderr, "INFO keyspace: %v\n", err)
os.Exit(1)
}
used := make(map[int]int64)
for _, line := range strings.Split(s, "\r\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
if !strings.HasPrefix(line, "db") {
continue
}
colon := strings.IndexByte(line, ':')
if colon <= 2 {
continue
}
dbStr := line[2:colon]
dbNum, err := strconv.Atoi(dbStr)
if err != nil {
continue
}
var keys int64
for _, part := range strings.Split(line[colon+1:], ",") {
part = strings.TrimSpace(part)
if strings.HasPrefix(part, "keys=") {
keys, _ = strconv.ParseInt(strings.TrimPrefix(part, "keys="), 10, 64)
break
}
}
used[dbNum] = keys
}
fmt.Printf("Redis %s — 逻辑库 key 统计(INFO keyspace)\n\n", addr)
for i := 0; i < 16; i++ {
k, ok := used[i]
if !ok {
fmt.Printf(" db%-2d : 无记录(通常为 0 个 key / 未写入过)\n", i)
} else if k == 0 {
fmt.Printf(" db%-2d : 0 keys\n", i)
} else {
fmt.Printf(" db%-2d : %d keys (已占用)\n", i, k)
}
}
fmt.Println()
fmt.Println("说明: Redis 默认 16 个逻辑库(0-15);「无记录」一般可当空闲选用。")
fmt.Println("若 redis.conf 里 databases=N实际库数量可能更大可用 redis-cli CONFIG GET databases 查看。")
}
package main
import (
"context"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/redis/go-redis/v9"
)
// 用法: go run . <host:port> [password]
// 例: go run . 10.1.1.100:6379
func main() {
addr := "10.1.1.100:6379"
pass := os.Getenv("REDIS_PASSWORD")
if len(os.Args) >= 2 {
addr = os.Args[1]
}
if len(os.Args) >= 3 {
pass = os.Args[2]
}
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
defer cancel()
r := redis.NewClient(&redis.Options{Addr: addr, Password: pass})
defer r.Close()
if err := r.Ping(ctx).Err(); err != nil {
fmt.Fprintf(os.Stderr, "连接失败 %s: %v\n", addr, err)
os.Exit(1)
}
s, err := r.Info(ctx, "keyspace").Result()
if err != nil {
fmt.Fprintf(os.Stderr, "INFO keyspace: %v\n", err)
os.Exit(1)
}
used := make(map[int]int64)
for _, line := range strings.Split(s, "\r\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
if !strings.HasPrefix(line, "db") {
continue
}
colon := strings.IndexByte(line, ':')
if colon <= 2 {
continue
}
dbStr := line[2:colon]
dbNum, err := strconv.Atoi(dbStr)
if err != nil {
continue
}
var keys int64
for _, part := range strings.Split(line[colon+1:], ",") {
part = strings.TrimSpace(part)
if strings.HasPrefix(part, "keys=") {
keys, _ = strconv.ParseInt(strings.TrimPrefix(part, "keys="), 10, 64)
break
}
}
used[dbNum] = keys
}
fmt.Printf("Redis %s — 逻辑库 key 统计(INFO keyspace)\n\n", addr)
for i := 0; i < 16; i++ {
k, ok := used[i]
if !ok {
fmt.Printf(" db%-2d : 无记录(通常为 0 个 key / 未写入过)\n", i)
} else if k == 0 {
fmt.Printf(" db%-2d : 0 keys\n", i)
} else {
fmt.Printf(" db%-2d : %d keys (已占用)\n", i, k)
}
}
fmt.Println()
fmt.Println("说明: Redis 默认 16 个逻辑库(0-15);「无记录」一般可当空闲选用。")
fmt.Println("若 redis.conf 里 databases=N实际库数量可能更大可用 redis-cli CONFIG GET databases 查看。")
}