feat(server): make server host and port configurable (#127)
* feat(server): make server host and port configurable Support configuring server binding address via environment variables and config files. Priority: ENV > local_config.yml > config.yml > default. - Add host and port options to system section in config.yml - Read SERVER_HOST and SERVER_PORT from environment variables - Default to 127.0.0.1:8002 for security Set host to "0.0.0.0" to allow LAN access. * test(server): add comprehensive tests for server binding config Add 28 test cases covering: - Environment variable priority (SERVER_HOST, SERVER_PORT) - Config file reading (system.host, system.port) - Default value fallback - OmegaConf integration - Edge cases (IPv6, empty values, invalid ports) * docs: add mobile/LAN access instructions - Add mobile access section to README and EN_README - Configure Vite to listen on 0.0.0.0 for LAN access in dev mode - Link to Issue #130 for mobile UI compatibility tracking
This commit is contained in:
42
EN_README.md
42
EN_README.md
@@ -171,6 +171,48 @@ If you have Docker installed, this is the easiest way:
|
||||
Frontend: `http://localhost:8123`
|
||||
Backend API: `http://localhost:8002`
|
||||
|
||||
### 📱 Mobile / LAN Access
|
||||
|
||||
You can access the game from other devices on the same network (e.g., phone, tablet).
|
||||
|
||||
> ⚠️ **Note**: The mobile UI is not optimized yet. See [Issue #130](https://github.com/4thfever/cultivation-world-simulator/issues/130).
|
||||
|
||||
**Configuration steps:**
|
||||
|
||||
1. Add to `static/local_config.yml`:
|
||||
```yaml
|
||||
system:
|
||||
host: "0.0.0.0" # Allow LAN access
|
||||
```
|
||||
|
||||
2. If using dev mode (`--dev`), also add to `web/vite.config.ts` in the `server` config:
|
||||
```typescript
|
||||
server: {
|
||||
host: '0.0.0.0', // Add this line
|
||||
proxy: { ... }
|
||||
}
|
||||
```
|
||||
|
||||
3. After starting the server, access from your phone:
|
||||
```
|
||||
http://<your-computer-lan-ip>:5173 # Dev mode
|
||||
http://<your-computer-lan-ip>:8002 # Production mode
|
||||
```
|
||||
|
||||
4. Find your computer's LAN IP:
|
||||
```bash
|
||||
# macOS
|
||||
ipconfig getifaddr en0
|
||||
|
||||
# Linux
|
||||
hostname -I
|
||||
|
||||
# Windows
|
||||
ipconfig
|
||||
```
|
||||
|
||||
> 💡 Make sure your phone and computer are on the same WiFi, and the firewall allows the corresponding port.
|
||||
|
||||
|
||||
## 📊 Project Status
|
||||
|
||||
|
||||
42
README.md
42
README.md
@@ -175,6 +175,48 @@
|
||||
前端:`http://localhost:8123`
|
||||
后端 API:`http://localhost:8002`
|
||||
|
||||
### 📱 手机/局域网访问
|
||||
|
||||
支持从局域网内的其他设备(如手机、平板)访问游戏。
|
||||
|
||||
> ⚠️ **注意**:移动端 UI 目前未做适配优化,体验可能不佳。详见 [Issue #130](https://github.com/4thfever/cultivation-world-simulator/issues/130)。
|
||||
|
||||
**配置步骤:**
|
||||
|
||||
1. 在 `static/local_config.yml` 中添加:
|
||||
```yaml
|
||||
system:
|
||||
host: "0.0.0.0" # 允许局域网访问
|
||||
```
|
||||
|
||||
2. 如果使用开发模式(`--dev`),还需在 `web/vite.config.ts` 的 `server` 配置中添加:
|
||||
```typescript
|
||||
server: {
|
||||
host: '0.0.0.0', // 添加这一行
|
||||
proxy: { ... }
|
||||
}
|
||||
```
|
||||
|
||||
3. 启动服务器后,在手机浏览器访问:
|
||||
```
|
||||
http://<电脑局域网IP>:5173 # 开发模式
|
||||
http://<电脑局域网IP>:8002 # 生产模式
|
||||
```
|
||||
|
||||
4. 查看电脑局域网 IP:
|
||||
```bash
|
||||
# macOS
|
||||
ipconfig getifaddr en0
|
||||
|
||||
# Linux
|
||||
hostname -I
|
||||
|
||||
# Windows
|
||||
ipconfig
|
||||
```
|
||||
|
||||
> 💡 确保手机和电脑连接同一个 WiFi,且防火墙已放行对应端口。
|
||||
|
||||
|
||||
## 📊 项目状态
|
||||
|
||||
|
||||
@@ -660,7 +660,8 @@ async def lifespan(app: FastAPI):
|
||||
asyncio.create_task(game_loop())
|
||||
|
||||
npm_process = None
|
||||
host = "127.0.0.1"
|
||||
# 从环境变量或配置文件读取 host。
|
||||
host = os.environ.get("SERVER_HOST") or getattr(getattr(CONFIG, "system", None), "host", None) or "127.0.0.1"
|
||||
|
||||
if IS_DEV_MODE:
|
||||
print("🚀 启动开发模式 (Dev Mode)...")
|
||||
@@ -1863,10 +1864,14 @@ else:
|
||||
|
||||
def start():
|
||||
"""启动服务的入口函数"""
|
||||
# 改为 8002 端口
|
||||
# 使用 127.0.0.1 更加安全且避免防火墙弹窗
|
||||
# 注意:直接传递 app 对象而不是字符串,避免 PyInstaller 打包后找不到模块的问题
|
||||
uvicorn.run(app, host="127.0.0.1", port=8002)
|
||||
# 从环境变量或配置文件读取服务器配置。
|
||||
# 优先级:环境变量 > 配置文件 > 默认值。
|
||||
# 设置 host 为 "0.0.0.0" 可允许局域网访问。
|
||||
host = os.environ.get("SERVER_HOST") or getattr(getattr(CONFIG, "system", None), "host", None) or "127.0.0.1"
|
||||
port = int(os.environ.get("SERVER_PORT") or getattr(getattr(CONFIG, "system", None), "port", None) or 8002)
|
||||
|
||||
# 注意:直接传递 app 对象而不是字符串,避免 PyInstaller 打包后找不到模块的问题。
|
||||
uvicorn.run(app, host=host, port=port)
|
||||
|
||||
if __name__ == "__main__":
|
||||
start()
|
||||
@@ -60,6 +60,8 @@ frontend:
|
||||
cloud_freq: low
|
||||
system:
|
||||
language: zh-CN
|
||||
host: "127.0.0.1" # 服务器绑定地址,设为 "0.0.0.0" 允许局域网访问。
|
||||
port: 8002 # 服务器端口。
|
||||
|
||||
play:
|
||||
base_benefit_probability: 0.05
|
||||
|
||||
446
tests/test_server_binding.py
Normal file
446
tests/test_server_binding.py
Normal file
@@ -0,0 +1,446 @@
|
||||
"""
|
||||
Tests for configurable server host and port binding.
|
||||
|
||||
These tests verify:
|
||||
- Environment variable configuration (SERVER_HOST, SERVER_PORT)
|
||||
- Config file configuration (system.host, system.port)
|
||||
- Priority: ENV > config file > default values
|
||||
- Default fallback behavior
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
|
||||
class TestServerHostConfiguration:
|
||||
"""Tests for server host configuration in lifespan and start functions."""
|
||||
|
||||
def test_host_from_env_variable(self):
|
||||
"""Test SERVER_HOST environment variable takes highest priority."""
|
||||
mock_config = MagicMock()
|
||||
mock_config.system.host = "192.168.1.100"
|
||||
|
||||
with patch.dict(os.environ, {"SERVER_HOST": "0.0.0.0"}), \
|
||||
patch("src.server.main.CONFIG", mock_config):
|
||||
|
||||
# Simulate the logic used in main.py.
|
||||
host = os.environ.get("SERVER_HOST") or getattr(
|
||||
getattr(mock_config, "system", None), "host", None
|
||||
) or "127.0.0.1"
|
||||
|
||||
assert host == "0.0.0.0"
|
||||
|
||||
def test_host_from_config_when_no_env(self):
|
||||
"""Test config file host is used when no environment variable."""
|
||||
mock_system = MagicMock()
|
||||
mock_system.host = "192.168.1.100"
|
||||
mock_config = MagicMock()
|
||||
mock_config.system = mock_system
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
# Remove SERVER_HOST if it exists.
|
||||
os.environ.pop("SERVER_HOST", None)
|
||||
|
||||
host = os.environ.get("SERVER_HOST") or getattr(
|
||||
getattr(mock_config, "system", None), "host", None
|
||||
) or "127.0.0.1"
|
||||
|
||||
assert host == "192.168.1.100"
|
||||
|
||||
def test_host_default_when_no_config(self):
|
||||
"""Test default 127.0.0.1 is used when no env or config."""
|
||||
mock_config = MagicMock()
|
||||
mock_config.system = None
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
os.environ.pop("SERVER_HOST", None)
|
||||
|
||||
host = os.environ.get("SERVER_HOST") or getattr(
|
||||
getattr(mock_config, "system", None), "host", None
|
||||
) or "127.0.0.1"
|
||||
|
||||
assert host == "127.0.0.1"
|
||||
|
||||
def test_host_default_when_system_has_no_host(self):
|
||||
"""Test default is used when system section exists but has no host."""
|
||||
mock_system = MagicMock(spec=[]) # Empty spec means no attributes.
|
||||
mock_config = MagicMock()
|
||||
mock_config.system = mock_system
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
os.environ.pop("SERVER_HOST", None)
|
||||
|
||||
host = os.environ.get("SERVER_HOST") or getattr(
|
||||
getattr(mock_config, "system", None), "host", None
|
||||
) or "127.0.0.1"
|
||||
|
||||
assert host == "127.0.0.1"
|
||||
|
||||
def test_env_overrides_config_host(self):
|
||||
"""Test environment variable overrides config file value."""
|
||||
mock_system = MagicMock()
|
||||
mock_system.host = "10.0.0.1"
|
||||
mock_config = MagicMock()
|
||||
mock_config.system = mock_system
|
||||
|
||||
with patch.dict(os.environ, {"SERVER_HOST": "0.0.0.0"}):
|
||||
host = os.environ.get("SERVER_HOST") or getattr(
|
||||
getattr(mock_config, "system", None), "host", None
|
||||
) or "127.0.0.1"
|
||||
|
||||
assert host == "0.0.0.0"
|
||||
|
||||
|
||||
class TestServerPortConfiguration:
|
||||
"""Tests for server port configuration in start function."""
|
||||
|
||||
def test_port_from_env_variable(self):
|
||||
"""Test SERVER_PORT environment variable takes highest priority."""
|
||||
mock_config = MagicMock()
|
||||
mock_config.system.port = 9000
|
||||
|
||||
with patch.dict(os.environ, {"SERVER_PORT": "8080"}):
|
||||
port = int(
|
||||
os.environ.get("SERVER_PORT") or getattr(
|
||||
getattr(mock_config, "system", None), "port", None
|
||||
) or 8002
|
||||
)
|
||||
|
||||
assert port == 8080
|
||||
|
||||
def test_port_from_config_when_no_env(self):
|
||||
"""Test config file port is used when no environment variable."""
|
||||
mock_system = MagicMock()
|
||||
mock_system.port = 9000
|
||||
mock_config = MagicMock()
|
||||
mock_config.system = mock_system
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
os.environ.pop("SERVER_PORT", None)
|
||||
|
||||
port = int(
|
||||
os.environ.get("SERVER_PORT") or getattr(
|
||||
getattr(mock_config, "system", None), "port", None
|
||||
) or 8002
|
||||
)
|
||||
|
||||
assert port == 9000
|
||||
|
||||
def test_port_default_when_no_config(self):
|
||||
"""Test default 8002 is used when no env or config."""
|
||||
mock_config = MagicMock()
|
||||
mock_config.system = None
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
os.environ.pop("SERVER_PORT", None)
|
||||
|
||||
port = int(
|
||||
os.environ.get("SERVER_PORT") or getattr(
|
||||
getattr(mock_config, "system", None), "port", None
|
||||
) or 8002
|
||||
)
|
||||
|
||||
assert port == 8002
|
||||
|
||||
def test_port_as_string_converted_to_int(self):
|
||||
"""Test port from env variable (string) is converted to int."""
|
||||
with patch.dict(os.environ, {"SERVER_PORT": "3000"}):
|
||||
port = int(
|
||||
os.environ.get("SERVER_PORT") or 8002
|
||||
)
|
||||
|
||||
assert port == 3000
|
||||
assert isinstance(port, int)
|
||||
|
||||
|
||||
class TestStartFunction:
|
||||
"""Tests for the start() function server binding."""
|
||||
|
||||
def test_start_uses_default_host_and_port(self):
|
||||
"""Test start() uses default values when no config."""
|
||||
from src.server import main
|
||||
|
||||
mock_config = MagicMock()
|
||||
mock_config.system = None
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True), \
|
||||
patch.object(main, "CONFIG", mock_config), \
|
||||
patch.object(main, "uvicorn") as mock_uvicorn:
|
||||
|
||||
os.environ.pop("SERVER_HOST", None)
|
||||
os.environ.pop("SERVER_PORT", None)
|
||||
|
||||
main.start()
|
||||
|
||||
mock_uvicorn.run.assert_called_once()
|
||||
call_kwargs = mock_uvicorn.run.call_args
|
||||
assert call_kwargs[1]["host"] == "127.0.0.1"
|
||||
assert call_kwargs[1]["port"] == 8002
|
||||
|
||||
def test_start_uses_env_variables(self):
|
||||
"""Test start() uses environment variables when set."""
|
||||
from src.server import main
|
||||
|
||||
mock_config = MagicMock()
|
||||
mock_system = MagicMock()
|
||||
mock_system.host = "10.0.0.1"
|
||||
mock_system.port = 9000
|
||||
mock_config.system = mock_system
|
||||
|
||||
with patch.dict(os.environ, {"SERVER_HOST": "0.0.0.0", "SERVER_PORT": "8080"}), \
|
||||
patch.object(main, "CONFIG", mock_config), \
|
||||
patch.object(main, "uvicorn") as mock_uvicorn:
|
||||
|
||||
main.start()
|
||||
|
||||
mock_uvicorn.run.assert_called_once()
|
||||
call_kwargs = mock_uvicorn.run.call_args
|
||||
assert call_kwargs[1]["host"] == "0.0.0.0"
|
||||
assert call_kwargs[1]["port"] == 8080
|
||||
|
||||
def test_start_uses_config_values(self):
|
||||
"""Test start() uses config file values when no env variables."""
|
||||
from src.server import main
|
||||
|
||||
mock_system = MagicMock()
|
||||
mock_system.host = "192.168.0.1"
|
||||
mock_system.port = 3000
|
||||
mock_config = MagicMock()
|
||||
mock_config.system = mock_system
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True), \
|
||||
patch.object(main, "CONFIG", mock_config), \
|
||||
patch.object(main, "uvicorn") as mock_uvicorn:
|
||||
|
||||
os.environ.pop("SERVER_HOST", None)
|
||||
os.environ.pop("SERVER_PORT", None)
|
||||
|
||||
main.start()
|
||||
|
||||
mock_uvicorn.run.assert_called_once()
|
||||
call_kwargs = mock_uvicorn.run.call_args
|
||||
assert call_kwargs[1]["host"] == "192.168.0.1"
|
||||
assert call_kwargs[1]["port"] == 3000
|
||||
|
||||
def test_start_env_overrides_config(self):
|
||||
"""Test environment variables override config file in start()."""
|
||||
from src.server import main
|
||||
|
||||
mock_system = MagicMock()
|
||||
mock_system.host = "10.0.0.1"
|
||||
mock_system.port = 9000
|
||||
mock_config = MagicMock()
|
||||
mock_config.system = mock_system
|
||||
|
||||
# Only set SERVER_HOST, not SERVER_PORT.
|
||||
with patch.dict(os.environ, {"SERVER_HOST": "0.0.0.0"}, clear=True), \
|
||||
patch.object(main, "CONFIG", mock_config), \
|
||||
patch.object(main, "uvicorn") as mock_uvicorn:
|
||||
|
||||
os.environ.pop("SERVER_PORT", None)
|
||||
|
||||
main.start()
|
||||
|
||||
call_kwargs = mock_uvicorn.run.call_args
|
||||
# HOST from env.
|
||||
assert call_kwargs[1]["host"] == "0.0.0.0"
|
||||
# PORT from config.
|
||||
assert call_kwargs[1]["port"] == 9000
|
||||
|
||||
|
||||
class TestLifespanHostConfiguration:
|
||||
"""Tests for host configuration in lifespan function."""
|
||||
|
||||
def test_lifespan_host_from_env(self):
|
||||
"""Test lifespan uses SERVER_HOST environment variable."""
|
||||
from src.server import main
|
||||
|
||||
mock_config = MagicMock()
|
||||
mock_system = MagicMock()
|
||||
mock_system.host = "10.0.0.1"
|
||||
mock_config.system = mock_system
|
||||
|
||||
with patch.dict(os.environ, {"SERVER_HOST": "0.0.0.0"}), \
|
||||
patch.object(main, "CONFIG", mock_config):
|
||||
|
||||
# Simulate the logic in lifespan.
|
||||
host = os.environ.get("SERVER_HOST") or getattr(
|
||||
getattr(main.CONFIG, "system", None), "host", None
|
||||
) or "127.0.0.1"
|
||||
|
||||
assert host == "0.0.0.0"
|
||||
|
||||
def test_lifespan_host_from_config(self):
|
||||
"""Test lifespan uses config host when no env variable."""
|
||||
from src.server import main
|
||||
|
||||
mock_system = MagicMock()
|
||||
mock_system.host = "192.168.1.50"
|
||||
mock_config = MagicMock()
|
||||
mock_config.system = mock_system
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True), \
|
||||
patch.object(main, "CONFIG", mock_config):
|
||||
|
||||
os.environ.pop("SERVER_HOST", None)
|
||||
|
||||
host = os.environ.get("SERVER_HOST") or getattr(
|
||||
getattr(main.CONFIG, "system", None), "host", None
|
||||
) or "127.0.0.1"
|
||||
|
||||
assert host == "192.168.1.50"
|
||||
|
||||
|
||||
class TestOmegaConfIntegration:
|
||||
"""Tests using actual OmegaConf configuration objects."""
|
||||
|
||||
def test_omegaconf_host_access(self):
|
||||
"""Test accessing host from OmegaConf config object."""
|
||||
config = OmegaConf.create({
|
||||
"system": {
|
||||
"language": "zh-CN",
|
||||
"host": "0.0.0.0",
|
||||
"port": 8002
|
||||
}
|
||||
})
|
||||
|
||||
host = getattr(getattr(config, "system", None), "host", None)
|
||||
assert host == "0.0.0.0"
|
||||
|
||||
def test_omegaconf_port_access(self):
|
||||
"""Test accessing port from OmegaConf config object."""
|
||||
config = OmegaConf.create({
|
||||
"system": {
|
||||
"language": "zh-CN",
|
||||
"host": "127.0.0.1",
|
||||
"port": 9000
|
||||
}
|
||||
})
|
||||
|
||||
port = getattr(getattr(config, "system", None), "port", None)
|
||||
assert port == 9000
|
||||
|
||||
def test_omegaconf_missing_system_section(self):
|
||||
"""Test graceful handling when system section is missing."""
|
||||
config = OmegaConf.create({
|
||||
"game": {"init_npc_num": 10}
|
||||
})
|
||||
|
||||
host = getattr(getattr(config, "system", None), "host", None) or "127.0.0.1"
|
||||
port = getattr(getattr(config, "system", None), "port", None) or 8002
|
||||
|
||||
assert host == "127.0.0.1"
|
||||
assert port == 8002
|
||||
|
||||
def test_omegaconf_missing_host_key(self):
|
||||
"""Test graceful handling when host key is missing from system."""
|
||||
config = OmegaConf.create({
|
||||
"system": {
|
||||
"language": "zh-CN"
|
||||
}
|
||||
})
|
||||
|
||||
host = getattr(getattr(config, "system", None), "host", None) or "127.0.0.1"
|
||||
assert host == "127.0.0.1"
|
||||
|
||||
def test_omegaconf_merged_config_priority(self):
|
||||
"""Test config merge priority (local_config overrides base config)."""
|
||||
base_config = OmegaConf.create({
|
||||
"system": {
|
||||
"language": "zh-CN",
|
||||
"host": "127.0.0.1",
|
||||
"port": 8002
|
||||
}
|
||||
})
|
||||
|
||||
local_config = OmegaConf.create({
|
||||
"system": {
|
||||
"host": "0.0.0.0"
|
||||
}
|
||||
})
|
||||
|
||||
# Simulate the merge behavior (local overrides base).
|
||||
merged = OmegaConf.merge(base_config, local_config)
|
||||
|
||||
assert merged.system.host == "0.0.0.0"
|
||||
assert merged.system.port == 8002 # From base.
|
||||
assert merged.system.language == "zh-CN" # From base.
|
||||
|
||||
|
||||
class TestEdgeCases:
|
||||
"""Tests for edge cases and error handling."""
|
||||
|
||||
def test_empty_env_variable_uses_config(self):
|
||||
"""Test empty string env variable falls through to config."""
|
||||
mock_system = MagicMock()
|
||||
mock_system.host = "10.0.0.1"
|
||||
mock_config = MagicMock()
|
||||
mock_config.system = mock_system
|
||||
|
||||
# Empty string is falsy in Python.
|
||||
with patch.dict(os.environ, {"SERVER_HOST": ""}):
|
||||
host = os.environ.get("SERVER_HOST") or getattr(
|
||||
getattr(mock_config, "system", None), "host", None
|
||||
) or "127.0.0.1"
|
||||
|
||||
assert host == "10.0.0.1"
|
||||
|
||||
def test_invalid_port_raises_error(self):
|
||||
"""Test invalid port string raises ValueError."""
|
||||
with patch.dict(os.environ, {"SERVER_PORT": "not_a_number"}):
|
||||
with pytest.raises(ValueError):
|
||||
int(os.environ.get("SERVER_PORT"))
|
||||
|
||||
def test_port_zero_is_valid(self):
|
||||
"""Test port 0 (random port) is accepted."""
|
||||
with patch.dict(os.environ, {"SERVER_PORT": "0"}):
|
||||
port = int(os.environ.get("SERVER_PORT") or 8002)
|
||||
assert port == 0
|
||||
|
||||
def test_high_port_number(self):
|
||||
"""Test high port numbers are accepted."""
|
||||
with patch.dict(os.environ, {"SERVER_PORT": "65535"}):
|
||||
port = int(os.environ.get("SERVER_PORT") or 8002)
|
||||
assert port == 65535
|
||||
|
||||
def test_host_ipv6_address(self):
|
||||
"""Test IPv6 address is accepted as host."""
|
||||
with patch.dict(os.environ, {"SERVER_HOST": "::"}):
|
||||
host = os.environ.get("SERVER_HOST") or "127.0.0.1"
|
||||
assert host == "::"
|
||||
|
||||
def test_host_localhost_string(self):
|
||||
"""Test 'localhost' string is accepted as host."""
|
||||
with patch.dict(os.environ, {"SERVER_HOST": "localhost"}):
|
||||
host = os.environ.get("SERVER_HOST") or "127.0.0.1"
|
||||
assert host == "localhost"
|
||||
|
||||
|
||||
class TestConfigYamlDefaults:
|
||||
"""Tests to verify the default values in config.yml."""
|
||||
|
||||
def test_config_yml_has_default_host(self):
|
||||
"""Test config.yml contains default host value."""
|
||||
import yaml
|
||||
|
||||
config_path = "static/config.yml"
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
assert "system" in config
|
||||
assert "host" in config["system"]
|
||||
assert config["system"]["host"] == "127.0.0.1"
|
||||
|
||||
def test_config_yml_has_default_port(self):
|
||||
"""Test config.yml contains default port value."""
|
||||
import yaml
|
||||
|
||||
config_path = "static/config.yml"
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
assert "system" in config
|
||||
assert "port" in config["system"]
|
||||
assert config["system"]["port"] == 8002
|
||||
@@ -26,6 +26,7 @@ export default defineConfig(({ mode }) => {
|
||||
assetsDir: 'web_static', // 避免与游戏原本的 /assets 目录冲突
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0', // 允许局域网访问
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: API_TARGET,
|
||||
|
||||
Reference in New Issue
Block a user