add: 新增虚拟环境支持和调试模式功能,优化日志系统
This commit is contained in:
parent
2d4f03b7cf
commit
284f4e39d7
|
|
@ -0,0 +1,44 @@
|
||||||
|
anyio==4.11.0
|
||||||
|
babel==2.17.0
|
||||||
|
beancount==3.2.0
|
||||||
|
beangulp==0.2.0
|
||||||
|
beanquery==0.2.0
|
||||||
|
beautifulsoup4==4.14.0
|
||||||
|
blinker==1.9.0
|
||||||
|
chardet==5.2.0
|
||||||
|
cheroot==10.0.1
|
||||||
|
click==8.3.0
|
||||||
|
dateparser==1.2.2
|
||||||
|
debugpy==1.8.16
|
||||||
|
fava==1.30.6
|
||||||
|
Flask==3.1.2
|
||||||
|
flask-babel==4.0.0
|
||||||
|
idna==3.10
|
||||||
|
iniconfig==2.1.0
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
jaraco.functools==4.3.0
|
||||||
|
Jinja2==3.1.6
|
||||||
|
lxml==6.0.2
|
||||||
|
markdown2==2.5.4
|
||||||
|
MarkupSafe==3.0.3
|
||||||
|
more-itertools==10.8.0
|
||||||
|
packaging==25.0
|
||||||
|
pluggy==1.6.0
|
||||||
|
ply==3.11
|
||||||
|
pycryptodomex==3.23.0
|
||||||
|
Pygments==2.19.2
|
||||||
|
pytest==8.4.2
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
python-magic==0.4.27
|
||||||
|
pytz==2025.2
|
||||||
|
pyzipper==0.3.6
|
||||||
|
regex==2025.9.18
|
||||||
|
simplejson==3.20.2
|
||||||
|
six==1.17.0
|
||||||
|
sniffio==1.3.1
|
||||||
|
soupsieve==2.8
|
||||||
|
TatSu-LTS==5.13.2
|
||||||
|
typing_extensions==4.15.0
|
||||||
|
tzlocal==5.3.1
|
||||||
|
watchfiles==1.1.0
|
||||||
|
Werkzeug==3.1.3
|
||||||
|
|
@ -6,8 +6,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/beancount-gs/utils/venv" // 添加这个导入
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -20,6 +22,14 @@ var ledgerAccountTypesMap map[string]map[string]string
|
||||||
var ledgerCurrencyMap map[string][]LedgerCurrency
|
var ledgerCurrencyMap map[string][]LedgerCurrency
|
||||||
var whiteList []string
|
var whiteList []string
|
||||||
|
|
||||||
|
var (
|
||||||
|
// 添加虚拟环境相关的变量
|
||||||
|
venvPath string
|
||||||
|
venvExecutor *venv.VenvExecutor
|
||||||
|
venvPathLock sync.RWMutex
|
||||||
|
venvExecLock sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Id string `json:"id,omitempty"`
|
Id string `json:"id,omitempty"`
|
||||||
Mail string `json:"mail,omitempty"`
|
Mail string `json:"mail,omitempty"`
|
||||||
|
|
@ -30,6 +40,7 @@ type Config struct {
|
||||||
IsBak bool `json:"isBak"`
|
IsBak bool `json:"isBak"`
|
||||||
OpeningBalances string `json:"openingBalances"`
|
OpeningBalances string `json:"openingBalances"`
|
||||||
CreateDate string `json:"createDate,omitempty"`
|
CreateDate string `json:"createDate,omitempty"`
|
||||||
|
DebugMode bool `json:"debugMode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
|
|
@ -85,12 +96,15 @@ func GetServerConfig() Config {
|
||||||
|
|
||||||
func LoadServerConfig() error {
|
func LoadServerConfig() error {
|
||||||
filePath := GetServerConfigFilePath()
|
filePath := GetServerConfigFilePath()
|
||||||
|
LogSystemInfo("Load config file (" + filePath + ")")
|
||||||
if !FileIfExist(filePath) {
|
if !FileIfExist(filePath) {
|
||||||
serverConfig = Config{
|
serverConfig = Config{
|
||||||
OpeningBalances: "Equity:OpeningBalances",
|
OpeningBalances: "Equity:Opening-Balances",
|
||||||
OperatingCurrency: "CNY",
|
OperatingCurrency: "CNY",
|
||||||
StartDate: "1970-01-01",
|
StartDate: "1970-01-01",
|
||||||
IsBak: true,
|
IsBak: true,
|
||||||
|
DebugMode: false, // 添加默认值
|
||||||
|
DataPath: GetDataPath(), // 添加默认值
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -131,15 +145,52 @@ func LoadServerConfig() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取当前调试模式状态
|
||||||
|
func IsDebugMode() bool {
|
||||||
|
return serverConfig.DebugMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置调试模式并保存到配置文件
|
||||||
|
func SetDebugMode(debug bool) error {
|
||||||
|
serverConfig.DebugMode = debug
|
||||||
|
return UpdateServerConfig(serverConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为方便使用,添加调试日志函数
|
||||||
|
func DebugLog(format string, args ...interface{}) {
|
||||||
|
if IsDebugMode() {
|
||||||
|
message := fmt.Sprintf(format, args...)
|
||||||
|
LogSystemInfo("[DEBUG] " + message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加带上下文信息的调试日志
|
||||||
|
func DebugLogWithContext(context string, format string, args ...interface{}) {
|
||||||
|
if IsDebugMode() {
|
||||||
|
message := fmt.Sprintf("[%s] "+format, append([]interface{}{context}, args...)...)
|
||||||
|
LogSystemInfo("[DEBUG] " + message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加带上下文信息的警告日志
|
||||||
|
func WarnLogWithContext(context string, format string, args ...interface{}) {
|
||||||
|
message := fmt.Sprintf("[%s] "+format, append([]interface{}{context}, args...)...)
|
||||||
|
LogSystemInfo("[WARN] " + message)
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateServerConfig(config Config) error {
|
func UpdateServerConfig(config Config) error {
|
||||||
bytes, err := json.Marshal(config)
|
bytes, err := json.Marshal(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = WriteFile(GetServerConfigFilePath(), string(bytes))
|
|
||||||
|
// 使用新的带目录创建功能的写入函数
|
||||||
|
configPath := GetServerConfigFilePath()
|
||||||
|
err = WriteFileWithDir(configPath, string(bytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
serverConfig = config
|
serverConfig = config
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -335,15 +386,16 @@ func LoadLedgerAccounts(ledgerId string) error {
|
||||||
key := words[2]
|
key := words[2]
|
||||||
temp = accountMap[key]
|
temp = accountMap[key]
|
||||||
account := Account{Acc: key, Type: nil, StartDate: "", EndDate: ""}
|
account := Account{Acc: key, Type: nil, StartDate: "", EndDate: ""}
|
||||||
if words[1] == "open" {
|
switch words[1] {
|
||||||
|
case "open":
|
||||||
// 最晚的开户日期设置为账户开户日期
|
// 最晚的开户日期设置为账户开户日期
|
||||||
account.StartDate = getMaxDate(words[0], temp.StartDate)
|
account.StartDate = getMaxDate(words[0], temp.StartDate)
|
||||||
// 货币单位
|
// 货币单位
|
||||||
if len(words) >= 4 {
|
if len(words) >= 4 {
|
||||||
account.Currency = words[3]
|
account.Currency = words[3]
|
||||||
}
|
}
|
||||||
} else if words[1] == "close" {
|
case "close":
|
||||||
//账户最晚的关闭日期设置为账户关闭日期
|
// 账户最晚的关闭日期设置为账户关闭日期
|
||||||
account.EndDate = getMaxDate(words[0], temp.EndDate)
|
account.EndDate = getMaxDate(words[0], temp.EndDate)
|
||||||
}
|
}
|
||||||
if account.EndDate != "" && account.StartDate == getMaxDate(account.StartDate, account.EndDate) {
|
if account.EndDate != "" && account.StartDate == getMaxDate(account.StartDate, account.EndDate) {
|
||||||
|
|
@ -588,3 +640,31 @@ func GetAccountIconName(account string) string {
|
||||||
nodes := strings.Split(account, ":")
|
nodes := strings.Split(account, ":")
|
||||||
return strings.Join(nodes, "_")
|
return strings.Join(nodes, "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增函数:设置虚拟环境路径
|
||||||
|
func SetVenvPath(path string) {
|
||||||
|
venvPathLock.Lock()
|
||||||
|
defer venvPathLock.Unlock()
|
||||||
|
venvPath = path
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增函数:获取虚拟环境路径
|
||||||
|
func GetVenvPath() string {
|
||||||
|
venvPathLock.RLock()
|
||||||
|
defer venvPathLock.RUnlock()
|
||||||
|
return venvPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增函数:设置虚拟环境执行器
|
||||||
|
func SetVenvExecutor(executor *venv.VenvExecutor) {
|
||||||
|
venvExecLock.Lock()
|
||||||
|
defer venvExecLock.Unlock()
|
||||||
|
venvExecutor = executor
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增函数:获取虚拟环境执行器
|
||||||
|
func GetVenvExecutor() *venv.VenvExecutor {
|
||||||
|
venvExecLock.RLock()
|
||||||
|
defer venvExecLock.RUnlock()
|
||||||
|
return venvExecutor
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -295,10 +295,7 @@ func getAccountWithNumber(str string) string {
|
||||||
|
|
||||||
func IsComment(line string) bool {
|
func IsComment(line string) bool {
|
||||||
trimmed := strings.TrimLeft(line, " ")
|
trimmed := strings.TrimLeft(line, " ")
|
||||||
if strings.HasPrefix(trimmed, ";") {
|
return strings.HasPrefix(trimmed, ";")
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除指定行范围的内容
|
// 删除指定行范围的内容
|
||||||
|
|
@ -360,3 +357,20 @@ func WriteToFile(filePath string, lines []string) error {
|
||||||
LogSystemInfo("Success write content in file " + filePath)
|
LogSystemInfo("Success write content in file " + filePath)
|
||||||
return writer.Flush()
|
return writer.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnsureDirExists 确保目录存在,如果不存在则创建
|
||||||
|
func EnsureDirExists(path string) error {
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
return os.MkdirAll(dir, 0755)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFileWithDir 写入文件,确保目录存在
|
||||||
|
func WriteFileWithDir(filename string, data string) error {
|
||||||
|
// 确保目录存在
|
||||||
|
if err := EnsureDirExists(filename); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
return WriteFile(filename, data)
|
||||||
|
}
|
||||||
|
|
|
||||||
118
script/log.go
118
script/log.go
|
|
@ -5,18 +5,136 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Info级别日志函数组
|
||||||
|
|
||||||
|
// LogInfo 记录信息级别的日志
|
||||||
|
// ledgerName: 账本名称,用于标识日志来源
|
||||||
|
// message: 需要记录的日志信息
|
||||||
func LogInfo(ledgerName string, message string) {
|
func LogInfo(ledgerName string, message string) {
|
||||||
fmt.Printf("[Info] [%s] [%s]: %s\n", time.Now().Format("2006-01-02 15:04:05"), ledgerName, message)
|
fmt.Printf("[Info] [%s] [%s]: %s\n", time.Now().Format("2006-01-02 15:04:05"), ledgerName, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogSystemInfo 记录系统信息日志
|
||||||
|
// message: 要记录的系统信息消息
|
||||||
func LogSystemInfo(message string) {
|
func LogSystemInfo(message string) {
|
||||||
LogInfo("System", message)
|
LogInfo("System", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Warn级别日志函数组
|
||||||
|
|
||||||
|
// LogWarn 记录警告级别的日志
|
||||||
|
// ledgerName: 账本名称,用于标识日志来源
|
||||||
|
// context: 日志上下文,提供更多分类信息
|
||||||
|
// format: 格式化字符串,定义日志消息格式
|
||||||
|
// args: 可变参数,用于填充格式化字符串
|
||||||
|
func LogWarn(ledgerName string, context string, format string, args ...interface{}) {
|
||||||
|
message := fmt.Sprintf(format, args...)
|
||||||
|
fmt.Printf("[Warn] [%s] [%s][%s]: %s\n",
|
||||||
|
time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
ledgerName,
|
||||||
|
context,
|
||||||
|
message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogSystemWarn 记录系统级警告日志
|
||||||
|
// context: 日志上下文标识
|
||||||
|
// format: 格式化字符串
|
||||||
|
// args: 格式化参数
|
||||||
|
func LogSystemWarn(context string, format string, args ...interface{}) {
|
||||||
|
LogWarn("System", context, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error级别日志函数组
|
||||||
|
|
||||||
|
// LogError 记录错误日志,格式为:[Error] [时间] [账本名称]: 错误信息
|
||||||
|
// ledgerName: 账本名称
|
||||||
|
// message: 需要记录的错误信息
|
||||||
func LogError(ledgerName string, message string) {
|
func LogError(ledgerName string, message string) {
|
||||||
fmt.Printf("[Error] [%s] [%s]: %s\n", time.Now().Format("2006-01-02 15:04:05"), ledgerName, message)
|
fmt.Printf("[Error] [%s] [%s]: %s\n", time.Now().Format("2006-01-02 15:04:05"), ledgerName, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogErrorDetailed 记录带有详细信息的错误日志
|
||||||
|
// ledgerName: 账本名称,用于标识日志来源
|
||||||
|
// context: 上下文信息,帮助定位错误发生的场景
|
||||||
|
// format: 格式化字符串,用于构建错误信息
|
||||||
|
// args: 格式化字符串的参数
|
||||||
|
// 日志格式为:[Error] [时间] [账本名称][上下文]: 错误信息
|
||||||
|
func LogErrorDetailed(ledgerName string, context string, format string, args ...interface{}) {
|
||||||
|
message := fmt.Sprintf(format, args...)
|
||||||
|
fmt.Printf("[Error] [%s] [%s][%s]: %s\n",
|
||||||
|
time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
ledgerName,
|
||||||
|
context,
|
||||||
|
message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogSystemError 记录系统级错误信息
|
||||||
|
// message: 错误描述信息
|
||||||
|
// 该函数是对LogError的封装,专门用于记录系统模块的错误日志
|
||||||
func LogSystemError(message string) {
|
func LogSystemError(message string) {
|
||||||
LogError("System", message)
|
LogError("System", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogSystemErrorDetailed 记录系统级错误日志,包含详细上下文信息
|
||||||
|
// context: 错误发生的上下文环境
|
||||||
|
// format: 格式化字符串,用于描述错误信息
|
||||||
|
// args: 格式化字符串的参数
|
||||||
|
func LogSystemErrorDetailed(context string, format string, args ...interface{}) {
|
||||||
|
LogErrorDetailed("System", context, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug级别日志函数组
|
||||||
|
|
||||||
|
// LogDebug 在调试模式下记录调试日志,格式为:[Debug] [时间] [账本名称]: 消息
|
||||||
|
// 仅当 IsDebugMode() 返回 true 时才会输出日志
|
||||||
|
func LogDebug(ledgerName string, message string) {
|
||||||
|
if IsDebugMode() {
|
||||||
|
fmt.Printf("[Debug] [%s] [%s]: %s\n", time.Now().Format("2006-01-02 15:04:05"), ledgerName, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogDebugDetailed 在调试模式下记录详细的调试日志
|
||||||
|
// ledgerName: 账本名称,用于标识日志来源
|
||||||
|
// context: 上下文信息,提供额外的日志分类
|
||||||
|
// format: 格式化字符串,用于构建日志消息
|
||||||
|
// args: 格式化字符串的参数
|
||||||
|
// 日志格式: [Debug] [时间] [账本名称][上下文]: 消息内容
|
||||||
|
// 注意: 仅在调试模式(IsDebugMode返回true)下输出日志
|
||||||
|
func LogDebugDetailed(ledgerName string, context string, format string, args ...interface{}) {
|
||||||
|
if IsDebugMode() {
|
||||||
|
message := fmt.Sprintf(format, args...)
|
||||||
|
fmt.Printf("[Debug] [%s] [%s][%s]: %s\n",
|
||||||
|
time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
ledgerName,
|
||||||
|
context,
|
||||||
|
message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogSystemDebug 记录系统级别的调试日志
|
||||||
|
// message: 需要记录的调试信息
|
||||||
|
func LogSystemDebug(message string) {
|
||||||
|
LogDebug("System", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogSystemDebugDetailed 记录系统级调试日志,包含详细上下文信息
|
||||||
|
// context: 日志上下文标识
|
||||||
|
// format: 日志格式字符串
|
||||||
|
// args: 格式化参数
|
||||||
|
func LogSystemDebugDetailed(context string, format string, args ...interface{}) {
|
||||||
|
LogDebugDetailed("System", context, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊功能日志函数
|
||||||
|
|
||||||
|
// LogBQLQueryDebug 在调试模式下记录BQL查询日志
|
||||||
|
// ledgerName: 账本名称
|
||||||
|
// context: 查询上下文信息
|
||||||
|
// format: 日志格式字符串
|
||||||
|
// args: 格式化参数
|
||||||
|
// 注意: 仅在调试模式开启时才会记录日志
|
||||||
|
func LogBQLQueryDebug(ledgerName string, context string, format string, args ...interface{}) {
|
||||||
|
if IsDebugMode() {
|
||||||
|
LogDebugDetailed(ledgerName, "BQL:"+context, format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
90
server.go
90
server.go
|
|
@ -3,14 +3,25 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/beancount-gs/script"
|
|
||||||
"github.com/beancount-gs/service"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/beancount-gs/script"
|
||||||
|
"github.com/beancount-gs/service"
|
||||||
|
"github.com/beancount-gs/utils/venv"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 全局变量,方便其他模块使用
|
||||||
|
var venvExecutor *venv.VenvExecutor
|
||||||
|
var venvPath string // 新增:虚拟环境路径变量
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 初始化服务器文件
|
||||||
|
* 检查账本目录是否存在,如果不存在则创建
|
||||||
|
* 返回error,表示操作是否成功
|
||||||
|
*/
|
||||||
func InitServerFiles() error {
|
func InitServerFiles() error {
|
||||||
dataPath := script.GetServerConfig().DataPath
|
dataPath := script.GetServerConfig().DataPath
|
||||||
// 账本目录不存在,则创建
|
// 账本目录不存在,则创建
|
||||||
|
|
@ -20,6 +31,11 @@ func InitServerFiles() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 加载服务器缓存
|
||||||
|
* 加载账本配置映射和账户映射
|
||||||
|
* 返回error,表示加载过程中是否出错
|
||||||
|
*/
|
||||||
func LoadServerCache() error {
|
func LoadServerCache() error {
|
||||||
err := script.LoadLedgerConfigMap()
|
err := script.LoadLedgerConfigMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -28,6 +44,11 @@ func LoadServerCache() error {
|
||||||
return script.LoadLedgerAccountsMap()
|
return script.LoadLedgerAccountsMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 授权中间件
|
||||||
|
* 检查请求头中的ledgerId是否有效
|
||||||
|
* 如果有效则继续处理请求,否则返回未授权错误
|
||||||
|
*/
|
||||||
func AuthorizedHandler() gin.HandlerFunc {
|
func AuthorizedHandler() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
ledgerId := c.GetHeader("ledgerId")
|
ledgerId := c.GetHeader("ledgerId")
|
||||||
|
|
@ -42,18 +63,24 @@ func AuthorizedHandler() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 注册路由
|
||||||
|
* 配置静态文件服务、API路由和需要授权的路由组
|
||||||
|
*/
|
||||||
func RegisterRouter(router *gin.Engine) {
|
func RegisterRouter(router *gin.Engine) {
|
||||||
// fix wildcard and static file router conflict, https://github.com/gin-gonic/gin/issues/360
|
// fix wildcard and static file router conflict, https://github.com/gin-gonic/gin/issues/360
|
||||||
router.GET("/", func(c *gin.Context) {
|
router.GET("/", func(c *gin.Context) {
|
||||||
c.Redirect(http.StatusMovedPermanently, "/web")
|
c.Redirect(http.StatusMovedPermanently, "/web")
|
||||||
})
|
})
|
||||||
router.StaticFS("/web", http.Dir("./public"))
|
router.StaticFS("/web", http.Dir("./public"))
|
||||||
|
// 公开API路由,无需授权
|
||||||
router.GET("/api/version", service.QueryVersion)
|
router.GET("/api/version", service.QueryVersion)
|
||||||
router.POST("/api/check", service.CheckBeancount)
|
router.POST("/api/check", service.CheckBeancount)
|
||||||
router.GET("/api/config", service.QueryServerConfig)
|
router.GET("/api/config", service.QueryServerConfig)
|
||||||
router.POST("/api/config", service.UpdateServerConfig)
|
router.POST("/api/config", service.UpdateServerConfig)
|
||||||
router.GET("/api/ledger", service.QueryLedgerList)
|
router.GET("/api/ledger", service.QueryLedgerList)
|
||||||
router.POST("/api/ledger", service.OpenOrCreateLedger)
|
router.POST("/api/ledger", service.OpenOrCreateLedger)
|
||||||
|
// 需要授权的API路由组
|
||||||
authorized := router.Group("/api/auth/")
|
authorized := router.Group("/api/auth/")
|
||||||
authorized.Use(AuthorizedHandler())
|
authorized.Use(AuthorizedHandler())
|
||||||
{
|
{
|
||||||
|
|
@ -106,19 +133,71 @@ func RegisterRouter(router *gin.Engine) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initVenvExecutor 初始化虚拟环境执行器
|
||||||
|
func initVenvExecutor(venvDir string) {
|
||||||
|
venvPath = venvDir
|
||||||
|
script.SetVenvPath(venvDir) // 设置路径到 script 包
|
||||||
|
|
||||||
|
// 检查虚拟环境是否存在
|
||||||
|
if !venv.CheckVenvExists(venvPath) {
|
||||||
|
script.LogSystemError("虚拟环境不存在,请先运行 setup script: " + venvPath)
|
||||||
|
fmt.Println("警告: 虚拟环境不存在,某些功能可能无法正常工作")
|
||||||
|
fmt.Println("请运行: ./start_dev.sh 或手动创建虚拟环境")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
venvExecutor = venv.NewVenvExecutor(venvPath)
|
||||||
|
script.SetVenvExecutor(venvExecutor) // 设置执行器到 script 包
|
||||||
|
|
||||||
|
// 测试 bean-query 是否可用
|
||||||
|
_, err := venvExecutor.GetCommandPath("bean-query")
|
||||||
|
if err != nil {
|
||||||
|
script.LogSystemError("bean-query 不可用: " + err.Error())
|
||||||
|
fmt.Println("警告: bean-query 命令不可用,价格查询功能将受限")
|
||||||
|
} else {
|
||||||
|
script.LogSystemInfo("虚拟环境初始化成功: bean-query 可用, 路径: " + venvPath)
|
||||||
|
fmt.Println("虚拟环境初始化成功: " + venvPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var secret string
|
var secret string
|
||||||
var port int
|
var port int
|
||||||
|
var debugFlag bool
|
||||||
|
var venvDir string // 新增:虚拟环境目录参数
|
||||||
|
|
||||||
flag.StringVar(&secret, "secret", "", "服务器密钥")
|
flag.StringVar(&secret, "secret", "", "服务器密钥")
|
||||||
flag.IntVar(&port, "p", 10000, "端口号")
|
flag.IntVar(&port, "p", 10000, "端口号")
|
||||||
|
flag.BoolVar(&debugFlag, "debug", false, "调试模式")
|
||||||
|
flag.StringVar(&venvDir, "venv", ".env_beancount-v3", "虚拟环境目录名称,默认值为 .env_beancount-v3") // 新增参数
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// 初始化虚拟环境执行器
|
||||||
|
initVenvExecutor(venvDir)
|
||||||
|
|
||||||
// 读取配置文件
|
// 读取配置文件
|
||||||
err := script.LoadServerConfig()
|
err := script.LoadServerConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
script.LogSystemError("Failed to load server config, " + err.Error())
|
script.LogSystemError("Failed to load server config, " + err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果命令行指定了debug参数,覆盖配置文件中的设置
|
||||||
|
if debugFlag {
|
||||||
|
err = script.SetDebugMode(true)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Warning: Failed to set debug mode:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 现在可以在任何地方使用 script.IsDebugMode() 来检查调试模式
|
||||||
|
if script.IsDebugMode() {
|
||||||
|
fmt.Println("调试模式已启用")
|
||||||
|
} else {
|
||||||
|
fmt.Println("调试模式未启用")
|
||||||
|
}
|
||||||
|
|
||||||
serverConfig := script.GetServerConfig()
|
serverConfig := script.GetServerConfig()
|
||||||
// 若 DataPath == "" 则配置未初始化
|
// 若 DataPath == "" 则配置未初始化
|
||||||
if serverConfig.DataPath != "" {
|
if serverConfig.DataPath != "" {
|
||||||
|
|
@ -135,11 +214,13 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// gin 日志设置
|
// gin 日志设置
|
||||||
gin.DisableConsoleColor()
|
gin.DisableConsoleColor()
|
||||||
fs, _ := os.Create("logs/gin.log")
|
fs, _ := os.Create("logs/gin.log")
|
||||||
gin.DefaultWriter = io.MultiWriter(fs, os.Stdout)
|
gin.DefaultWriter = io.MultiWriter(fs, os.Stdout)
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
// 注册路由
|
// 注册路由
|
||||||
RegisterRouter(router)
|
RegisterRouter(router)
|
||||||
|
|
||||||
|
|
@ -151,10 +232,13 @@ func main() {
|
||||||
startLog += " or http://" + ip + portStr
|
startLog += " or http://" + ip + portStr
|
||||||
}
|
}
|
||||||
script.LogSystemInfo(startLog)
|
script.LogSystemInfo(startLog)
|
||||||
|
|
||||||
// 打开浏览器
|
// 打开浏览器
|
||||||
script.OpenBrowser(url)
|
script.OpenBrowser(url)
|
||||||
|
|
||||||
// 打印密钥
|
// 打印密钥
|
||||||
script.LogSystemInfo("Secret token is " + script.GenerateServerSecret(secret))
|
script.LogSystemInfo("Secret token is " + script.GenerateServerSecret(secret))
|
||||||
|
|
||||||
// 启动服务
|
// 启动服务
|
||||||
err = router.Run(portStr)
|
err = router.Run(portStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* @Author: liangzai450
|
||||||
|
* @Date: 2025-09-17 20:50:11
|
||||||
|
* @LastEditors: liangzai liangzai450@qq.com
|
||||||
|
* @LastEditTime: 2025-09-18 08:15:42
|
||||||
|
* @FilePath: \\cnb-beancount\\utils\\venv\\venv.go
|
||||||
|
* @Description:
|
||||||
|
* Copyright (c) 2025 by ${git_name_email}, All Rights Reserved.
|
||||||
|
* ==============================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
package venv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VenvExecutor 虚拟环境执行器
|
||||||
|
type VenvExecutor struct {
|
||||||
|
venvPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVenvExecutor 创建新的虚拟环境执行器
|
||||||
|
func NewVenvExecutor(venvPath string) *VenvExecutor {
|
||||||
|
return &VenvExecutor{venvPath: venvPath}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommandPath 获取虚拟环境中命令的完整路径
|
||||||
|
func (v *VenvExecutor) GetCommandPath(command string) (string, error) {
|
||||||
|
var cmdPath string
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
cmdPath = filepath.Join(v.venvPath, "Scripts", command+".exe")
|
||||||
|
} else {
|
||||||
|
cmdPath = filepath.Join(v.venvPath, "bin", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(cmdPath); os.IsNotExist(err) {
|
||||||
|
return "", fmt.Errorf("command %s not found at: %s", command, cmdPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute 执行虚拟环境中的命令
|
||||||
|
func (v *VenvExecutor) Execute(command string, args ...string) error {
|
||||||
|
cmdPath, err := v.GetCommandPath(command)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(cmdPath, args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteWithOutput 执行命令并返回输出
|
||||||
|
func (v *VenvExecutor) ExecuteWithOutput(command string, args ...string) ([]byte, []byte, error) {
|
||||||
|
cmdPath, err := v.GetCommandPath(command)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(cmdPath, args...)
|
||||||
|
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
|
err = cmd.Run()
|
||||||
|
return stdout.Bytes(), stderr.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeanQueryStdout 执行 bean-query 命令,只返回纯净的标准输出
|
||||||
|
func (v *VenvExecutor) BeanQueryStdout(beancountFile, query string) ([]byte, error) {
|
||||||
|
stdout, stderr, err := v.ExecuteWithOutput("bean-query", beancountFile, query)
|
||||||
|
if err != nil {
|
||||||
|
// 你可以选择记录 stderr 或根据错误类型处理
|
||||||
|
fmt.Printf("Bean-query stderr: %s", string(stderr))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stdout, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeanQuery 执行 bean-query 命令
|
||||||
|
// 保持原有的 BeanQuery 函数(如果需要兼容旧代码)
|
||||||
|
func (v *VenvExecutor) BeanQuery(beancountFile, query string) ([]byte, error) {
|
||||||
|
return v.BeanQueryStdout(beancountFile, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeanCheck 执行 bean-check 语法检查
|
||||||
|
func (v *VenvExecutor) BeanCheck(beancountFile string) error {
|
||||||
|
_, err := v.BeanQueryStdout("bean-check", beancountFile)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fava 启动 fava 服务器
|
||||||
|
func (v *VenvExecutor) Fava(beancountFile string, port int) error {
|
||||||
|
return v.Execute("fava", beancountFile, "--port", fmt.Sprintf("%d", port))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckVenvExists 检查虚拟环境是否存在
|
||||||
|
func CheckVenvExists(venvPath string) bool {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return dirExists(filepath.Join(venvPath, "Scripts"))
|
||||||
|
}
|
||||||
|
return dirExists(filepath.Join(venvPath, "bin"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func dirExists(path string) bool {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return info.IsDir()
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue