mirror of
https://github.com/gazer-x/komari.git
synced 2026-06-22 00:05:52 +08:00
feat: #11 添加Geoip,Custom CSS、JS 合并为CustomHead
This commit is contained in:
@@ -8,8 +8,10 @@
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# local development data
|
||||
komari.db
|
||||
/data
|
||||
/utils/geoip/data
|
||||
.vscode/
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/komari-monitor/komari/common"
|
||||
"github.com/komari-monitor/komari/database/clients"
|
||||
"github.com/komari-monitor/komari/database/config"
|
||||
"github.com/komari-monitor/komari/utils/geoip"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -22,6 +26,23 @@ func UploadBasicInfo(c *gin.Context) {
|
||||
}
|
||||
|
||||
cbi.UUID = uuid
|
||||
|
||||
if cfg, err := config.Get(); err != nil && cfg.GeoIpEnabled {
|
||||
if cbi.IPv4 != "" {
|
||||
ip4 := net.ParseIP(cbi.IPv4)
|
||||
ip4_record, _ := geoip.GetGeoIpInfo(ip4)
|
||||
if ip4_record != nil {
|
||||
cbi.Country = geoip.GetCountryUnicodeEmoji(ip4_record.Country.ISOCode)
|
||||
}
|
||||
} else if cbi.IPv6 != "" {
|
||||
ip6 := net.ParseIP(cbi.IPv6)
|
||||
ip6_record, _ := geoip.GetGeoIpInfo(ip6)
|
||||
if ip6_record != nil {
|
||||
cbi.Country = geoip.GetCountryUnicodeEmoji(ip6_record.Country.ISOCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := clients.UpdateOrInsertBasicInfo(cbi); err != nil {
|
||||
c.JSON(500, gin.H{"status": "error", "error": err})
|
||||
return
|
||||
|
||||
@@ -60,14 +60,15 @@ type Config struct {
|
||||
Sitename string `json:"sitename,omitempty" gorm:"type:varchar(100);not null"`
|
||||
Description string `json:"desc,omitempty" gorm:"type:text"`
|
||||
AllowCros bool `json:"allow_cros" gorm:"default:false"`
|
||||
// GeoIP 配置
|
||||
GeoIpEnabled bool `json:"geoip_enable" gorm:"default:true"`
|
||||
GeoIpProvider string `json:"geoip_provider" gorm:"type:varchar(20);default:'mmdb'"` // mmdb, bilibili, ip-api. 暂时只实现了mmdb
|
||||
// OAuth 配置
|
||||
OAuthClientID string `json:"oauth_id" gorm:"type:varchar(255);not null"`
|
||||
OAuthClientSecret string `json:"oauth_secret" gorm:"type:varchar(255);not null"`
|
||||
OAuthRedirectURI string `json:"oauth_redirect_uri" gorm:"type:varchar(255);not null"`
|
||||
OAuthEnabled bool `json:"oauth_enable" gorm:"default:false"`
|
||||
// 自定义美化
|
||||
CustomCSS string `json:"custom_css" gorm:"type:longtext"`
|
||||
CustomJS string `json:"custom_js" gorm:"type:longtext"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
CustomHead string `json:"custom_head" gorm:"type:longtext"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ require (
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/oschwald/maxminddb-golang v1.13.1
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
|
||||
@@ -63,6 +63,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
+1
-2
@@ -47,8 +47,7 @@ func initIndex() {
|
||||
}
|
||||
func UpdateIndex(cfg models.Config) {
|
||||
replaceMap := map[string]string{
|
||||
"<!-- customize css -->": cfg.CustomCSS,
|
||||
"<!-- customize js -->": cfg.CustomJS,
|
||||
"<!-- customize head -->": cfg.CustomHead,
|
||||
}
|
||||
for k, v := range replaceMap {
|
||||
IndexFile = strings.Replace(RawIndexFile, k, v, 1)
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
)
|
||||
|
||||
var (
|
||||
GeoIpUrl = "https://gh-proxy.com/raw.githubusercontent.com/Loyalsoldier/geoip/release/GeoLite2-Country.mmdb"
|
||||
GeoIpFilePath = "./data/GeoLite2-Country.mmdb"
|
||||
geoIpDb *maxminddb.Reader = nil
|
||||
)
|
||||
|
||||
type GeoIpRecord struct {
|
||||
Country struct {
|
||||
ISOCode string `maxminddb:"iso_code"`
|
||||
Names map[string]string `maxminddb:"names"`
|
||||
} `maxminddb:"country"`
|
||||
}
|
||||
|
||||
// 更新Geoip数据库,使用 GeoIpUrl下载最新的数据库文件,并覆盖本地的 GeoIpFilePath 文件
|
||||
func UpdateGeoIpDatabase() error {
|
||||
if geoIpDb != nil {
|
||||
geoIpDb.Close()
|
||||
geoIpDb = nil
|
||||
}
|
||||
log.Println("Downloading GeoIP database...")
|
||||
resp, err := http.Get(GeoIpUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("failed to download file: %s", resp.Status)
|
||||
}
|
||||
|
||||
out, err := os.Create(GeoIpFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitGeoIp() {
|
||||
if os.MkdirAll("./data", os.ModePerm) != nil {
|
||||
return
|
||||
}
|
||||
if _, err := os.Stat(GeoIpFilePath); os.IsNotExist(err) {
|
||||
err := UpdateGeoIpDatabase()
|
||||
if err != nil {
|
||||
fmt.Println("Error updating GeoIP database:", err)
|
||||
} else {
|
||||
fmt.Println("GeoIP database updated successfully.")
|
||||
}
|
||||
}
|
||||
var err error
|
||||
geoIpDb, err = maxminddb.Open(GeoIpFilePath)
|
||||
if err != nil {
|
||||
log.Printf("Error opening GeoIP database: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetGeoIpInfo(ip net.IP) (*GeoIpRecord, error) {
|
||||
if geoIpDb == nil {
|
||||
InitGeoIp()
|
||||
}
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("IP address is nil")
|
||||
}
|
||||
var record GeoIpRecord
|
||||
err := geoIpDb.Lookup(ip, &record)
|
||||
if err != nil {
|
||||
log.Printf("Error looking up IP %s: %v", ip.String(), err)
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
func GetCountryUnicodeEmoji(isoCode string) string {
|
||||
if len(isoCode) != 2 {
|
||||
return ""
|
||||
}
|
||||
isoCode = strings.ToUpper(isoCode)
|
||||
|
||||
if !unicode.IsLetter(rune(isoCode[0])) || !unicode.IsLetter(rune(isoCode[1])) {
|
||||
return ""
|
||||
}
|
||||
|
||||
rune1 := rune(0x1F1E6 + (rune(isoCode[0]) - 'A'))
|
||||
rune2 := rune(0x1F1E6 + (rune(isoCode[1]) - 'A'))
|
||||
return string(rune1) + string(rune2)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package geoip_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/komari-monitor/komari/utils/geoip"
|
||||
)
|
||||
|
||||
// 测试GeoIP数据库的初始化和更新功能
|
||||
func TestInitGeoIp(t *testing.T) {
|
||||
geoip.InitGeoIp()
|
||||
|
||||
// 检查数据库
|
||||
fileInfo, err := os.Stat(geoip.GeoIpFilePath)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get file info: %v", err)
|
||||
}
|
||||
if fileInfo.Size() == 0 {
|
||||
t.Errorf("GeoIP database file is empty: %s", geoip.GeoIpFilePath)
|
||||
}
|
||||
|
||||
// IPv4
|
||||
ipaddr := "8.8.8.8"
|
||||
ip := net.ParseIP(ipaddr)
|
||||
record, err := geoip.GetGeoIpInfo(ip)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get GeoIP info for IP %s: %v", ipaddr, err)
|
||||
}
|
||||
|
||||
if record != nil {
|
||||
if record.Country.ISOCode == "" && record.Country.Names["zh-CN"] == "" {
|
||||
t.Errorf("Country information is missing for IP %s", ipaddr)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("GeoIP record is nil for IP %s", ipaddr)
|
||||
}
|
||||
|
||||
t.Logf("IPv4:[%s]%s - %s", ipaddr, record.Country.ISOCode, record.Country.Names["zh-CN"])
|
||||
|
||||
// IPv6
|
||||
ipaddr = "2001:4860:4860::8888"
|
||||
ip = net.ParseIP(ipaddr)
|
||||
record, err = geoip.GetGeoIpInfo(ip)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get GeoIP info for IPv6 %s: %v", ipaddr, err)
|
||||
}
|
||||
if record != nil {
|
||||
if record.Country.ISOCode == "" && record.Country.Names["zh-CN"] == "" {
|
||||
t.Errorf("Country information is missing for IPv6 %s", ipaddr)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("GeoIP record is nil for IPv6 %s", ipaddr)
|
||||
}
|
||||
t.Logf("IPv6:[%s]%s - %s", ipaddr, record.Country.ISOCode, record.Country.Names["zh-CN"])
|
||||
}
|
||||
|
||||
func TestUnicodeEmoji(t *testing.T) {
|
||||
ISOCode := "CN"
|
||||
emoji := geoip.GetCountryUnicodeEmoji(ISOCode)
|
||||
if emoji != "🇨🇳" {
|
||||
t.Errorf("Expected emoji for %s, got %s", ISOCode, emoji)
|
||||
}
|
||||
t.Logf("Emoji for %s: %s", ISOCode, emoji)
|
||||
}
|
||||
Reference in New Issue
Block a user