Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: onfork/sprov-ui
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: liveid-org/sprov-ui
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Able to merge. These branches can be automatically merged.

Commits on Feb 14, 2019

  1. Update README.md

    vaxilu authored Feb 14, 2019
    Copy the full SHA
    a42bea2 View commit details
  2. Update README.md

    vaxilu authored Feb 14, 2019
    Copy the full SHA
    d1f54b0 View commit details

Commits on Feb 16, 2019

  1. Update README.md

    vaxilu authored Feb 16, 2019
    Copy the full SHA
    a252961 View commit details
  2. 更新1.1.0版本

    增加对shadowsocks协议的支持
    sprov committed Feb 16, 2019
    Copy the full SHA
    1c47a41 View commit details
  3. Update install.sh

    sprov committed Feb 16, 2019
    Copy the full SHA
    595214a View commit details
  4. Update install.sh

    sprov committed Feb 16, 2019
    Copy the full SHA
    7a2a551 View commit details
  5. Update README.md

    vaxilu authored Feb 16, 2019
    Copy the full SHA
    4053ef2 View commit details

Commits on Feb 17, 2019

  1. v2.0.0

    重要更新:
    1、大幅降低内存使用
    2、大幅提升启动速度
    3、大幅降低jar包的大小
    4、使用jar包替代2.0版本以前的war包
    sprov committed Feb 17, 2019
    Copy the full SHA
    adedddf View commit details
  2. Update install.sh

    sprov committed Feb 17, 2019
    Copy the full SHA
    e5c882d View commit details
  3. Update README.md

    vaxilu authored Feb 17, 2019
    Copy the full SHA
    5fb5d60 View commit details

Commits on Feb 18, 2019

  1. Update accounts.html

    sprov committed Feb 18, 2019
    Copy the full SHA
    f4a79c4 View commit details

Commits on Feb 19, 2019

  1. v2.1.0

    sprov committed Feb 19, 2019
    Copy the full SHA
    557fc97 View commit details

Commits on Feb 20, 2019

  1. Update README.md

    vaxilu authored Feb 20, 2019
    Copy the full SHA
    8ad0340 View commit details
  2. v2.2.0

    增加dokodemo-door协议
    略微降低CPU使用率
    修复修改alterId导致v2ray重启失败的问题
    sprov committed Feb 20, 2019
    Copy the full SHA
    81fdcf0 View commit details
  3. Copy the full SHA
    21a3806 View commit details

Commits on Feb 22, 2019

  1. v2.3.0

    节点增加tag标识属性,注意每个tag必须唯一,否则会导致无法连接节点
    sprov committed Feb 22, 2019
    Copy the full SHA
    f7bf2de View commit details
  2. Update README.md

    vaxilu authored Feb 22, 2019
    Copy the full SHA
    679f628 View commit details

Commits on Feb 26, 2019

  1. v2.4.0

    增加更多kcp的详细配置
    增加websocket传输配置
    服务器状态刷新间隔由1.5秒调整为1秒
    增加一些配置方面的提示
    sprov committed Feb 26, 2019
    Copy the full SHA
    713c19e View commit details
  2. Copy the full SHA
    31216f1 View commit details

Commits on Mar 3, 2019

  1. v2.5.0

     - 增加mtproto协议
     - 增加复制链接、查看二维码功能
     - 略微降低sprov-ui面板的cpu使用率
    sprov committed Mar 3, 2019
    Copy the full SHA
    33a2516 View commit details

Commits on Mar 4, 2019

  1. v2.5.1

     - 减少软件包大小
     - 降低内存使用
     - 优化网页加载速度
    sprov committed Mar 4, 2019
    Copy the full SHA
    1afeb63 View commit details

Commits on Mar 8, 2019

  1. v2.5.2

     - 修复一个删除节点失败的问题
    sprov committed Mar 8, 2019
    Copy the full SHA
    1a8f2f5 View commit details

Commits on Mar 15, 2019

  1. v2.6.0

     - 增加 Websocket 的 tls 传输配置
     - vmess 链接和二维码中使用 tag 标识作为备注名
     - 增加面板自动更新检测(每30分钟从 Github 检测一次)
     - 增加重启面板、一键升级面板功能(升级后记得重启面板)
     - 增加 robots.txt,禁止所有搜索引擎收录
    sprov committed Mar 15, 2019
    Copy the full SHA
    707f75e View commit details

Commits on Mar 16, 2019

  1. Update README.md

    vaxilu authored Mar 16, 2019
    Copy the full SHA
    7191016 View commit details
  2. Update README.md

    vaxilu authored Mar 16, 2019
    Copy the full SHA
    8789f7a View commit details

Commits on Mar 17, 2019

  1. Update install.sh

    vaxilu authored Mar 17, 2019
    Copy the full SHA
    f8ef569 View commit details

Commits on Mar 27, 2019

  1. v2.7.0

     - 优化网页加载速度
     - 支持设置监听 IP
    sprov committed Mar 27, 2019
    Copy the full SHA
    bcd1fa8 View commit details
  2. Copy the full SHA
    c53e704 View commit details
  3. Update README.md

    vaxilu authored Mar 27, 2019
    Copy the full SHA
    2372093 View commit details
  4. Update pom.xml

    sprov committed Mar 27, 2019
    Copy the full SHA
    9dad098 View commit details
  5. Copy the full SHA
    cd61013 View commit details

Commits on Mar 28, 2019

  1. Update install.sh

    sprov committed Mar 28, 2019
    Copy the full SHA
    e81d65e View commit details

Commits on Mar 29, 2019

  1. Update install.sh

    vaxilu authored Mar 29, 2019
    Copy the full SHA
    1c341c7 View commit details
  2. Update README.md

    vaxilu authored Mar 29, 2019
    Copy the full SHA
    af71d10 View commit details

Commits on Apr 1, 2019

  1. Update README.md

    vaxilu authored Apr 1, 2019
    Copy the full SHA
    a8e0f8e View commit details

Commits on Apr 3, 2019

  1. Update README.md

    vaxilu authored Apr 3, 2019
    Copy the full SHA
    f0b97c7 View commit details
  2. v2.8.0

     - 新增流量统计(支持所有账号)
     - 新增手动重置流量功能
     - 账号去除tag标识(目前仍会显示在界面上,下个版本将不会显示)
     - 账号增加备注
    sprov committed Apr 3, 2019
    Copy the full SHA
    cd20d58 View commit details
  3. Copy the full SHA
    f90a5da View commit details
  4. Update README.md

    vaxilu authored Apr 3, 2019
    Copy the full SHA
    bdafef2 View commit details
  5. Update README.md

    vaxilu authored Apr 3, 2019
    Copy the full SHA
    2cb15f6 View commit details

Commits on Apr 5, 2019

  1. v2.9.0

     - 新增 socks 协议
     - 新增 http 协议
     - mtproto 增加备注
     - 显示 v2ray 总流量
     - 增加搜索账号功能
     - 删除 tag 标识
    sprov committed Apr 5, 2019
    Copy the full SHA
    da9c80d View commit details
  2. Copy the full SHA
    fd01676 View commit details

Commits on Apr 6, 2019

  1. v2.9.0

    sprov committed Apr 6, 2019
    Copy the full SHA
    df9f99d View commit details
  2. pic

    sprov committed Apr 6, 2019
    Copy the full SHA
    3f1c8b3 View commit details
  3. Update README.md

    vaxilu authored Apr 6, 2019
    Copy the full SHA
    b1ff37a View commit details

Commits on Apr 12, 2019

  1. Update README.md

    vaxilu authored Apr 12, 2019
    Copy the full SHA
    6612eee View commit details

Commits on Apr 16, 2019

  1. v2.10.0

     - 增加禁用、启用单个账号的功能(此功能会自动重启 v2ray)
     - 修复点击重启报错的问题
     - 加快面板启动速度
    sprov committed Apr 16, 2019
    Copy the full SHA
    39c1c10 View commit details
  2. Copy the full SHA
    24152d7 View commit details
  3. Update README.md

    vaxilu authored Apr 16, 2019
    Copy the full SHA
    a16ab88 View commit details
Showing with 3,744 additions and 728 deletions.
  1. BIN 1.png
  2. BIN 2.png
  3. BIN 3.png
  4. +36 −2 README.md
  5. +109 −30 install.sh
  6. +72 −44 pom.xml
  7. +0 −14 src/main/java/xyz/sprov/blog/sprovui/ServletInitializer.java
  8. +91 −0 src/main/java/xyz/sprov/blog/sprovui/SprovUISparkApp.java
  9. +0 −14 src/main/java/xyz/sprov/blog/sprovui/SprovUiApplication.java
  10. +0 −16 src/main/java/xyz/sprov/blog/sprovui/WebConfigurer.java
  11. +41 −0 src/main/java/xyz/sprov/blog/sprovui/bean/InboundTraffic.java
  12. +3 −11 src/main/java/xyz/sprov/blog/sprovui/bean/Msg.java
  13. +8 −0 src/main/java/xyz/sprov/blog/sprovui/bean/User.java
  14. +0 −56 src/main/java/xyz/sprov/blog/sprovui/controller/BaseController.java
  15. +204 −35 src/main/java/xyz/sprov/blog/sprovui/controller/InboundsController.java
  16. +10 −14 src/main/java/xyz/sprov/blog/sprovui/controller/ServerController.java
  17. +40 −0 src/main/java/xyz/sprov/blog/sprovui/controller/SprovUIController.java
  18. +66 −40 src/main/java/xyz/sprov/blog/sprovui/controller/V2rayController.java
  19. +1 −1 src/main/java/xyz/sprov/blog/sprovui/entity/Exec.java
  20. +11 −0 src/main/java/xyz/sprov/blog/sprovui/exception/SprovUIException.java
  21. +13 −0 src/main/java/xyz/sprov/blog/sprovui/filter/EncodingFilter.java
  22. +26 −0 src/main/java/xyz/sprov/blog/sprovui/filter/LoginFilter.java
  23. +0 −26 src/main/java/xyz/sprov/blog/sprovui/interceptor/LoginInterceptor.java
  24. +47 −0 src/main/java/xyz/sprov/blog/sprovui/route/BaseRoute.java
  25. +112 −0 src/main/java/xyz/sprov/blog/sprovui/route/InboundsRoute.java
  26. +15 −0 src/main/java/xyz/sprov/blog/sprovui/route/ServerRoute.java
  27. +30 −0 src/main/java/xyz/sprov/blog/sprovui/route/V2rayRoute.java
  28. +268 −0 src/main/java/xyz/sprov/blog/sprovui/service/ExtraConfigService.java
  29. +47 −0 src/main/java/xyz/sprov/blog/sprovui/service/ReportService.java
  30. +30 −22 src/main/java/xyz/sprov/blog/sprovui/service/ServerService.java
  31. +137 −0 src/main/java/xyz/sprov/blog/sprovui/service/SprovUIService.java
  32. +13 −0 src/main/java/xyz/sprov/blog/sprovui/service/ThreadService.java
  33. +221 −11 src/main/java/xyz/sprov/blog/sprovui/service/V2rayConfigService.java
  34. +31 −24 src/main/java/xyz/sprov/blog/sprovui/service/V2rayService.java
  35. +14 −0 src/main/java/xyz/sprov/blog/sprovui/transformer/JsonTransformer.java
  36. +60 −0 src/main/java/xyz/sprov/blog/sprovui/util/Config.java
  37. +42 −0 src/main/java/xyz/sprov/blog/sprovui/util/Context.java
  38. +5 −0 src/main/java/xyz/sprov/blog/sprovui/util/ExecUtil.java
  39. +72 −0 src/main/java/xyz/sprov/blog/sprovui/util/HttpUtil.java
  40. +0 −38 src/main/java/xyz/sprov/blog/sprovui/util/SessionContainer.java
  41. +20 −0 src/main/java/xyz/sprov/blog/sprovui/util/SessionUtil.java
  42. +50 −0 src/main/java/xyz/sprov/blog/sprovui/util/SparkUtil.java
  43. +98 −0 src/main/java/xyz/sprov/blog/sprovui/util/V2ctlUtil.java
  44. +3 −1 src/main/java/xyz/sprov/blog/sprovui/venum/Protocol.java
  45. +0 −7 src/main/resources/application-dev.yml
  46. +0 −15 src/main/resources/application.yml
  47. +1 −0 src/main/resources/static/res/base64/base64.min.js
  48. +36 −2 src/main/resources/static/res/js/application.js
  49. 0 src/main/resources/static/res/js/v2ray.js
  50. +683 −0 src/main/resources/static/res/js/v2ray/accounts.js
  51. +183 −0 src/main/resources/static/res/js/v2ray/index.js
  52. +7 −0 src/main/resources/static/res/qrcode/qrious.min.js
  53. +4 −2 src/main/resources/templates/common/common_js.html
  54. +25 −4 src/main/resources/templates/index.html
  55. +640 −210 src/main/resources/templates/v2ray/accounts.html
  56. +53 −6 src/main/resources/templates/v2ray/clients.html
  57. +1 −1 src/main/resources/templates/v2ray/footer.html
  58. +65 −82 src/main/resources/templates/v2ray/index.html
Binary file modified 1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified 2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed 3.png
Binary file not shown.
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,53 @@
# sprov-ui
一个v2ray Web面板
一个支持多协议多用户的v2ray Web面板

# 支持的功能
- 系统运行状态监控
- 多协议、多用户管理
- 禁用、启用单个账号
- 支持设置监听的 IP(多 IP 服务器下)
- 流量统计(支持所有协议)

## 支持的 v2ray 协议
- vmess(v2ray 特色)
- shadowsocks(经典 ss)
- mtproto(Telegram 专用)
- dokodemo-door(端口转发)
- socks(socks 4、socks 4a、socks 5)
- http(http 代理)

## 支持的 vmess 传输配置
- tcp
- kcp + 伪装
- ws + 伪装 + tls

# 运行截图
![1.png](1.png)
![2.png](2.png)

# 支持的系统
>务必使用纯净版的系统,建议在 256MB 内存及以上的 vps 搭建,低内存情况下可能运作不良
- CentOS 7(推荐)
- Ubuntu 16
- Ubuntu 18
- Debian 8
- Debian 9

# 一键安装
# 一键安装&升级面板
>面板已内置升级功能(每30分钟从 Github 检测一次)
以下两条命令皆可,两者是一样的,只需要运行一个,如果其中一个有错误,可以运行另外一个。
```
wget -O install.sh -N --no-check-certificate https://blog.sprov.xyz/sprov-ui.sh && bash install.sh
```
```
wget -O install.sh -N --no-check-certificate https://github.com/sprov065/sprov-ui/raw/master/install.sh && bash install.sh
```
# 详细教程
https://blog.sprov.xyz/2019/02/09/sprov-ui/

# Telegram 群组
https://t.me/sprov_blog

# Telegram 频道
https://t.me/sprov_channel
139 changes: 109 additions & 30 deletions install.sh
Original file line number Diff line number Diff line change
@@ -8,9 +8,7 @@ plain='\033[0m'
cur_dir=$(pwd)

# check root
[[ $EUID -ne 0 ]] && echo -e "${red}error:${plain} This script must be run as root!!!\n" && exit 1

[[ -d "/proc/vz" ]] && echo -e "${yellow}warning:${plain} Your VPS is based on OpenVZ, which is not support bbr.\n"
[[ $EUID -ne 0 ]] && echo -e "${red}错误:${plain} 必须使用root用户运行此脚本!\n" && exit 1

# check os
if [[ -f /etc/redhat-release ]]; then
@@ -28,7 +26,7 @@ elif cat /proc/version | grep -Eqi "ubuntu"; then
elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then
release="centos"
else
echo -e "${red}OS is not supported, please contact the author!!!${plain}\n" && exit 1
echo -e "${red}未检测到系统版本,请联系脚本作者!${plain}\n" && exit 1
fi

os_version=""
@@ -43,28 +41,53 @@ fi

if [[ x"${release}" == x"centos" ]]; then
if [[ ${os_version} -le 6 ]]; then
echo -e "${red}OS is not supported, please use CentOS 7 or higher versions!!!${plain}" && exit 1
echo -e "${red}请使用 CentOS 7 或更高版本的系统!${plain}\n" && exit 1
fi
elif [[ x"${release}" == x"ubuntu" ]]; then
if [[ ${os_version} -lt 16 ]]; then
echo -e "${red}OS is not supported, please use Ubuntu 16 or higher versions!!!${plain}\n" && exit 1
echo -e "${red}请使用 Ubuntu 16 或更高版本的系统!${plain}\n" && exit 1
fi
elif [[ x"${release}" == x"debian" ]]; then
if [[ ${os_version} -lt 8 ]]; then
echo -e "${red}OS is not supported, please use Debian 8 or higher versions!!!${plain}\n" && exit 1
echo -e "${red}请使用 Debian 8 或更高版本的系统!${plain}\n" && exit 1
fi
fi

install_bc() {
command -v bc >/dev/null 2>&1 || yum install bc -y || apt install bc -y
}

install_java() {
if [[ x"${release}" == x"centos" ]]; then
if [[ -f /usr/bin/java ]]; then
install_bc
java_version=`/usr/bin/java -version 2>&1 | awk -F '\"' 'NR==1{print $2}' | awk -F '.' '{OFS="."; print $1,$2;}'`
require_version=1.8
is_ok=`echo "$java_version>=$require_version" | bc`
if [[ is_ok -eq 1 ]]; then
echo -e "${green}已检测到1.8及以上版本的java,无需重复安装${plain}"
else
echo -e "错误:${green}/usr/bin/java${red}的版本低于1.8,请安装大于等于1.8版本的java${plain}"
exit -1
fi
elif [[ x"${release}" == x"centos" ]]; then
yum install java-1.8.0-openjdk curl -y
elif [[ x"${release}" == x"debian" || x"${release}" == x"ubuntu" ]]; then
apt install default-jre curl -y
fi
if [[ $? -ne 0 ]]; then
echo -e "${red}Java环境安装失败,请检查错误信息${plain}"
exit 1
fi
}

install_v2ray() {
echo -e "${green}开始安装or升级v2ray${plain}"
bash <(curl -L -s https://install.direct/go.sh) -f
if [[ $? -ne 0 ]]; then
echo -e "${red}v2ray安装或升级失败,请检查错误信息${plain}"
exit 1
fi
systemctl enable v2ray
systemctl start v2ray
}

@@ -82,23 +105,25 @@ close_firewall() {
fi
}

install_sprov-ui() {
if [[ ! -f /usr/local/sprov-ui ]]; then
mkdir /usr/local/sprov-ui
fi
wget -O /usr/local/sprov-ui/sprov-ui.war https://github.com/sprov065/sprov-ui/releases/download/v1.0.0-beta/sprov-ui-1.0.0.war
read -p "请输入面板监听端口[默认80]:" port
read -p "请输入面板登录用户名[默认sprov]:" user
read -p "请输入面板登录密码[默认blog.sprov.xyz]:" pwd
if [[ -z "${port}" ]]; then
port=80
fi
if [[ -z "${user}" ]]; then
user="sprov"
fi
if [[ -z "${pwd}" ]]; then
pwd="blog.sprov.xyz"
port=80
user="sprov"
pwd="blog.sprov.xyz"

init_config() {
if [[ ! -e "/etc/sprov-ui" ]]; then
mkdir /etc/sprov-ui
fi
echo "port=${port}" > /etc/sprov-ui/sprov-ui.conf
echo "username=${user}" >> /etc/sprov-ui/sprov-ui.conf
echo "password=${pwd}" >> /etc/sprov-ui/sprov-ui.conf

echo ""
echo -e "面板监听端口(不是v2ray端口):${green}${port}${plain}"
echo -e "面板登录用户名:${green}${user}${plain}"
echo -e "面板登录密码:${green}${pwd}${plain}"
}

init_service() {
echo "[Unit]" > /etc/systemd/system/sprov-ui.service
echo "Description=sprov-ui Service" >> /etc/systemd/system/sprov-ui.service
echo "After=network.target" >> /etc/systemd/system/sprov-ui.service
@@ -107,22 +132,76 @@ install_sprov-ui() {
echo "[Service]" >> /etc/systemd/system/sprov-ui.service
echo "Type=simple" >> /etc/systemd/system/sprov-ui.service
java_cmd="/usr/bin/java"
echo "ExecStart=${java_cmd} -jar /usr/local/sprov-ui/sprov-ui.war --server.port=${port} --user.username=${user} --user.password=${pwd}" >> /etc/systemd/system/sprov-ui.service
echo "ExecStart=${java_cmd} -jar /usr/local/sprov-ui/sprov-ui.jar" >> /etc/systemd/system/sprov-ui.service
echo "" >> /etc/systemd/system/sprov-ui.service
echo "[Install]" >> /etc/systemd/system/sprov-ui.service
echo "WantedBy=multi-user.target" >> /etc/systemd/system/sprov-ui.service
systemctl daemon-reload
echo -e "${green}v2ray面板安装成功${plain}\n"
echo -e "面板监听端口(不是v2ray端口):${green}${port}${plain}"
echo -e "面板登录用户名:${green}${user}${plain}"
echo -e "面板登录密码:${green}${pwd}${plain}"
}

set_systemd() {
init_service
reset="y"
first="y"
if [[ -f "/etc/sprov-ui/sprov-ui.conf" ]]; then
read -p "是否重新设置面板端口、用户名和密码[y/n]:" reset
first="n"
fi
if [[ x"$reset" == x"y" || x"$reset" == x"Y" ]]; then
read -p "请输入面板监听端口[默认80]:" port
read -p "请输入面板登录用户名[默认sprov]:" user
read -p "请输入面板登录密码[默认blog.sprov.xyz]:" pwd
if [[ -z "${port}" ]]; then
port=80
fi
if [[ -z "${user}" ]]; then
user="sprov"
fi
if [[ -z "${pwd}" ]]; then
pwd="blog.sprov.xyz"
fi
init_config
if [[ x"${first}" == x"n" ]]; then
echo ""
echo -e "${green}设置了新的端口、用户名和密码后记得重启面板${plain}"
fi
fi
}

install_sprov-ui() {
if [[ ! -e "/usr/local/sprov-ui" ]]; then
mkdir /usr/local/sprov-ui
fi
if [[ -f "/usr/local/sprov-ui/sprov-ui.war" ]]; then
rm /usr/local/sprov-ui/sprov-ui.war -f
fi
last_version=$(curl --silent "https://api.github.com/repos/sprov065/sprov-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
echo -e "检测到sprov-ui最新版本:${last_version},开始下载核心文件"
wget -N --no-check-certificate -O /usr/local/sprov-ui/sprov-ui.jar https://github.com/sprov065/sprov-ui/releases/download/${last_version}/sprov-ui-${last_version}.jar
if [[ $? -ne 0 ]]; then
echo -e "${red}下载sprov-ui核心文件失败,请确保你的服务器能够下载Github的文件,如果多次安装失败,请参考手动安装教程${plain}"
exit 1
fi
set_systemd
echo ""
echo -e "${green}sprov-ui面板安装成功${plain}\n"
echo ""
echo -e "开启面板:systemctl start sprov-ui"
echo -e "关闭面板:systemctl stop sprov-ui"
echo -e "重启面板:systemctl restart sprov-ui"
echo -e "运行状态:systemctl status sprov-ui"
echo -e "开机启动:systemctl enable sprov-ui"
echo -e "取消开机启动:systemctl disable sprov-ui"
echo ""
echo -e "若启动面板失败,请使用以下命令手动启动检查问题所在:"
echo -e "/usr/bin/java -jar /usr/local/sprov-ui/sprov-ui.jar"
echo ""
echo -e "若未安装bbr等加速工具,推荐使用以下命令一键安装bbr:"
echo -e "wget --no-check-certificate https://github.com/sprov065/blog/raw/master/bbr.sh && bash bbr.sh"
}

echo "开始安装"
install_java
install_v2ray
close_firewall
install_sprov-ui
install_sprov-ui
116 changes: 72 additions & 44 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -2,67 +2,79 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--<parent>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-parent</artifactId>-->
<!--<version>2.1.2.RELEASE</version>-->
<!--<relativePath/> &lt;!&ndash; lookup parent from repository &ndash;&gt;-->
<!--</parent>-->
<groupId>xyz.sprov.ui</groupId>
<artifactId>sprov-ui</artifactId>
<version>1.0.0</version>
<version>1.1.0</version>
<packaging>war</packaging>
<name>sprov-ui</name>
<description>使用网页端来管理你的v2ray</description>
<description>一个支持多协议多用户的v2ray Web面板</description>

<properties>
<java.version>1.8</java.version>
<springboot.version>2.1.2.RELEASE</springboot.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
<version>${springboot.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
<version>${springboot.version}</version>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.7.2</version>
</dependency>

<!--<dependency>-->
<!--<groupId>com.sparkjava</groupId>-->
<!--<artifactId>spark-template-thymeleaf</artifactId>-->
<!--<version>2.7.1</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>${springboot.version}</version>
<groupId>com.sparkjava</groupId>
<artifactId>spark-template-velocity</artifactId>
<version>2.7.1</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>${springboot.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${springboot.version}</version>
<scope>test</scope>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-web</artifactId>-->
<!--<exclusions>-->
<!--<exclusion>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-tomcat</artifactId>-->
<!--</exclusion>-->
<!--</exclusions>-->
<!--<version>${springboot.version}</version>-->
<!--</dependency>-->

<!--<dependency>-->
<!--<groupId>com.google.protobuf</groupId>-->
<!--<artifactId>protobuf-java</artifactId>-->
<!--<version>3.6.1</version>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-undertow</artifactId>-->
<!--<version>${springboot.version}</version>-->
<!--</dependency>-->

<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-thymeleaf</artifactId>-->
<!--<version>${springboot.version}</version>-->
<!--</dependency>-->

<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-devtools</artifactId>-->
<!--<version>${springboot.version}</version>-->
<!--<scope>runtime</scope>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-test</artifactId>-->
<!--<version>${springboot.version}</version>-->
<!--<scope>test</scope>-->
<!--</dependency>-->

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
@@ -79,14 +91,30 @@
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>

<dependency>
<groupId>in.zhaoj</groupId>
<artifactId>v2ray</artifactId>
<version>1.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<!--<plugin>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-maven-plugin</artifactId>-->
<!--<version>${springboot.version}</version>-->
<!--</plugin>-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springboot.version}</version>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
14 changes: 0 additions & 14 deletions src/main/java/xyz/sprov/blog/sprovui/ServletInitializer.java

This file was deleted.

91 changes: 91 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/SprovUISparkApp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package xyz.sprov.blog.sprovui;

import xyz.sprov.blog.sprovui.util.Config;
import xyz.sprov.blog.sprovui.util.SparkUtil;

import static spark.Spark.*;
import static xyz.sprov.blog.sprovui.util.Context.*;

public class SprovUISparkApp {

static {
System.setProperty("file.encoding", "UTF-8");
}

public static void main(String[] args) {
long start = System.currentTimeMillis();
int port = Config.getPort();
port(port);
threadPool(4, 1, 60000);

initExceptionHandler(e -> {
System.err.println("sprov-ui启动失败:" + e.getMessage());
System.exit(1);
});

exception(Exception.class, (e, request, response) -> System.out.println(e.getMessage()));

staticFiles.location("/static");
staticFiles.expireTime(3600 * 24 * 30 * 6);

before("", encodingFilter);
before("/*", encodingFilter);

get("/", baseRoute.index());
post("/login", baseRoute.login(), jsonTransformer);

get("/robots.txt", baseRoute.robots());

path("/v2ray", () -> {
before("", loginFilter);
before("/*", loginFilter);

get("", SparkUtil.view("/v2ray/index"));
get("/", SparkUtil.view("/v2ray/index"));
get("/accounts", SparkUtil.view("/v2ray/accounts"));
get("/accounts/", SparkUtil.view("/v2ray/accounts"));
get("/clients", SparkUtil.view("/v2ray/clients"));
get("/clients/", SparkUtil.view("/v2ray/clients"));

post("/status", v2rayRoute.status(), jsonTransformer);
post("/start", v2rayRoute.start(), jsonTransformer);
post("/restart", v2rayRoute.restart(), jsonTransformer);
post("/stop", v2rayRoute.stop(), jsonTransformer);
post("/config", v2rayRoute.config(), jsonTransformer);

path("/inbound", () -> {
post("/add", inboundsRoute.add(), jsonTransformer);
post("/edit", inboundsRoute.edit(), jsonTransformer);
post("/del", inboundsRoute.del(), jsonTransformer);

post("/openTraffic", inboundsRoute.openTraffic(), jsonTransformer);
post("/resetTraffic", inboundsRoute.resetTraffic(), jsonTransformer);
post("/resetAllTraffic", inboundsRoute.resetAllTraffic(), jsonTransformer);

post("/enable", inboundsRoute.enable(), jsonTransformer);
post("/disable", inboundsRoute.disable(), jsonTransformer);
post("/delDisabled", inboundsRoute.delDisabled(), jsonTransformer);
});
});

path("/server", () -> {
before("", loginFilter);
before("/*", loginFilter);

post("/status", serverRoute.status(), jsonTransformer);
});

path("/sprov-ui", () -> {
before("", loginFilter);
before("/*", loginFilter);

post("/isLastVersion", sprovUIController.isLastVersion(), jsonTransformer);
post("/update", sprovUIController.update(), jsonTransformer);
post("/restart", sprovUIController.restart(), jsonTransformer);
});
awaitInitialization();
long end = System.currentTimeMillis();
System.out.println("sprov-ui 启动成功,耗时 " + (end - start) + " ms,面板监听端口为 " + port);
}

}
14 changes: 0 additions & 14 deletions src/main/java/xyz/sprov/blog/sprovui/SprovUiApplication.java

This file was deleted.

16 changes: 0 additions & 16 deletions src/main/java/xyz/sprov/blog/sprovui/WebConfigurer.java

This file was deleted.

41 changes: 41 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/bean/InboundTraffic.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package xyz.sprov.blog.sprovui.bean;

public class InboundTraffic {

private String tag;
private long downlink;
private long uplink;

public String getTag() {
return tag;
}

public void setTag(String tag) {
this.tag = tag;
}

public long getDownlink() {
return downlink;
}

public void setDownlink(long downlink) {
this.downlink = downlink;
}

public long getUplink() {
return uplink;
}

public void setUplink(long uplink) {
this.uplink = uplink;
}

@Override
public String toString() {
return "InboundTraffic{" +
"tag='" + tag + '\'' +
", downlink=" + downlink +
", uplink=" + uplink +
'}';
}
}
14 changes: 3 additions & 11 deletions src/main/java/xyz/sprov/blog/sprovui/bean/Msg.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package xyz.sprov.blog.sprovui.bean;

import com.alibaba.fastjson.JSONObject;
import org.springframework.validation.BindingResult;
import com.alibaba.fastjson.serializer.SerializerFeature;
//import org.springframework.validation.BindingResult;

/**
* 保存从服务器返回到用户的消息
@@ -20,15 +21,6 @@ public Msg(String msg) {
this.msg = msg;
}

public Msg(BindingResult result) {
if (result.hasErrors()) {
this.success = false;
this.msg = result.getAllErrors().get(0).getDefaultMessage();
} else {
this.success = true;
}
}

public Msg(boolean success, String msg) {
this.success = success;
this.msg = msg;
@@ -71,6 +63,6 @@ public void setObj(Object obj) {

@Override
public String toString() {
return JSONObject.toJSONString(this);
return JSONObject.toJSONString(this, SerializerFeature.WriteMapNullValue);
}
}
8 changes: 8 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/bean/User.java
Original file line number Diff line number Diff line change
@@ -10,6 +10,14 @@ public class User implements Serializable {
private String username;
private String password;

public User() {
}

public User(String username, String password) {
this.username = username;
this.password = password;
}

public String getUsername() {
return username;
}

This file was deleted.

239 changes: 204 additions & 35 deletions src/main/java/xyz/sprov/blog/sprovui/controller/InboundsController.java
Original file line number Diff line number Diff line change
@@ -2,35 +2,39 @@

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import xyz.sprov.blog.sprovui.bean.Msg;
import xyz.sprov.blog.sprovui.exception.V2rayConfigException;
import xyz.sprov.blog.sprovui.service.ExtraConfigService;
import xyz.sprov.blog.sprovui.service.V2rayConfigService;
import xyz.sprov.blog.sprovui.service.V2rayService;
import xyz.sprov.blog.sprovui.util.Context;

import java.io.IOException;

@Controller
@RequestMapping("v2ray/inbound")
//@Controller
//@RequestMapping("v2ray/inbound")
public class InboundsController {

@Autowired
private V2rayConfigService configService;
// @Autowired
private V2rayConfigService configService = Context.v2rayConfigService;

private JSONObject getInbound(int port,
private ExtraConfigService extraConfigService = Context.extraConfigService;

private V2rayService v2rayService = Context.v2rayService;

private JSONObject getInbound(String listen,
int port,
String protocol,
String settings,
String streamSettings,
String tag) {
String remark) {
JSONObject inbound = new JSONObject();
inbound.put("listen", listen);
inbound.put("port", port);
inbound.put("protocol", protocol);
if (!StringUtils.isBlank(tag)) {
inbound.put("tag", tag);
}
inbound.put("remark", remark);
// if (!StringUtils.isBlank(tag)) {
// }
try {
inbound.put("settings", JSONObject.parseObject(settings));
} catch (Exception e) {
@@ -50,16 +54,17 @@ private JSONObject getInbound(int port,
* @param settings 一个JSON字符串
* @param streamSettings 一个JSON字符串
*/
@ResponseBody
@PostMapping("add")
public Msg add(int port,
// @ResponseBody
// @PostMapping("add")
public Msg add(String listen,
int port,
String protocol,
String settings,
String streamSettings,
String tag) {
String remark) {
JSONObject inbound;
try {
inbound = getInbound(port, protocol, settings, streamSettings, tag);
inbound = getInbound(listen, port, protocol, settings, streamSettings, remark);
} catch (V2rayConfigException e) {
return new Msg(false, e.getMessage());
}
@@ -78,16 +83,21 @@ public Msg add(int port,
}
}

@ResponseBody
@PostMapping("edit")
public Msg edit(int port,
// @ResponseBody
// @PostMapping("edit")
public Msg edit(String listen,
int port,
String protocol,
String settings,
String streamSettings,
String remark,
String tag) {
JSONObject inbound;
try {
inbound = getInbound(port, protocol, settings, streamSettings, tag);
inbound = getInbound(listen, port, protocol, settings, streamSettings, remark);
if (!StringUtils.isEmpty(tag)) {
inbound.put("tag", tag);
}
} catch (V2rayConfigException e) {
return new Msg(false, e.getMessage());
}
@@ -101,28 +111,187 @@ public Msg edit(int port,
}
}

@ResponseBody
@PostMapping("del")
// @ResponseBody
// @PostMapping("del")
public Msg del(int port) {
try {
configService.delInbound(port);
return new Msg(true, "删除成功,需重启v2ray生效");
} catch (Exception e) {
e.printStackTrace();
return new Msg(false, "删除失败:" + e.getMessage());
}
}

/**
* 开启 inbound 流量统计
*/
public Msg openTraffic(int port) {
try {
JSONObject inbound = configService.getInbound(port);
if (inbound == null) {
return new Msg(false, "此账号不存在");
}
inbound.put("tag", "inbound-" + port);
configService.editInbound(inbound);
return new Msg(true, "操作成功,需重启v2ray生效");
} catch (Exception e) {
e.printStackTrace();
return new Msg(false, "操作失败:" + e.getMessage());
}
}

/**
* 重置端口流量
*/
public Msg resetTraffic(int port) {
try {
JSONObject inbound = configService.getInbound(port);
if (inbound == null) {
return new Msg(false, "找不到端口为" + port + "的账号");
}
String tag = inbound.getString("tag");
if (StringUtils.isEmpty(tag)) {
return new Msg(false, "端口为" + port + "的账号没有tag标识");
}
extraConfigService.resetTraffic(tag);
return new Msg(true, "操作成功,此功能无需重启");
} catch (Exception e) {
e.printStackTrace();
return new Msg(false, "操作失败:" + e.getMessage());
}
}

/**
* 重置所有流量
*/
public Msg resetAllTraffic() {
try {
extraConfigService.resetAllTraffic();
return new Msg(true, "操作成功,此功能无需重启");
} catch (Exception e) {
e.printStackTrace();
return new Msg(false, "操作失败:" + e.getMessage());
}
}

/**
* 启用 inbound
*/
public Msg enable(int port) {
JSONObject inbound;
try {
// 从删除 disabled 列表删除 inbound
inbound = extraConfigService.delDisabledInbound(port);
if (inbound == null) {
return new Msg(false, "端口为 " + port + " 的 inbound 不存在");
}
} catch (Exception e) {
return new Msg(false, "操作失败:" + e.getMessage());
}
try {
// 添加流量配置文件
String tag = inbound.getString("tag");
if (!StringUtils.isEmpty(tag)) {
Long down = inbound.getLong("downlink");
Long up = inbound.getLong("uplink");
extraConfigService.addInbound(tag, down, up);
}
} catch (Exception e) {
try {
extraConfigService.addDisabledInbound(inbound);
} catch (Exception ignore) {}
return new Msg(false, "操作失败:" + e.getMessage());
}
try {
// 添加 v2ray 配置文件
inbound.remove("downlink");
inbound.remove("uplink");
configService.addInbound(inbound, false);
} catch (Exception e) {
try {
extraConfigService.addDisabledInbound(inbound);
} catch (Exception ignore) {}
return new Msg(false, "操作失败:" + e.getMessage());
}
try {
v2rayService.restart();
return new Msg(true, "操作成功,已自动重启 v2ray");
} catch (Exception e) {
return new Msg(false, "操作成功,但是重启 v2ray 失败,请手动重启");
}
}

/**
* 禁用 inbound
*/
public Msg disable(int port) {
JSONObject inbound;
try {
// 获取 inbound
inbound = configService.getInbound(port);
if (inbound == null) {
return new Msg(false, "端口为 " + port + " 的 inbound 不存在");
}
String tag = inbound.getString("tag");
JSONObject extraInbound = extraConfigService.getInboundByTag(tag);
if (extraInbound != null) {
inbound.putAll(extraInbound);
}
} catch (Exception e) {
return new Msg(false, "操作失败:" + e.getMessage());
}
try {
// 从 v2ray 配置文件中删除
configService.delInbound(port);
} catch (Exception e) {
return new Msg(false, "操作失败:" + e.getMessage());
}

try {
// 添加进 disabled 列表
extraConfigService.addDisabledInbound(inbound);
} catch (Exception e) {
try {
inbound.remove("downlink");
inbound.remove("uplink");
configService.addInbound(inbound, false);
} catch (Exception ignore) {}
return new Msg(false, "操作失败:" + e.getMessage());
}

try {
v2rayService.restart();
return new Msg(true, "操作成功,已自动重启 v2ray");
} catch (Exception e) {
return new Msg(false, "操作成功,但是重启 v2ray 失败,请手动重启");
}
}

/**
* 删除已禁用的账号
*/
public Msg delDisabled(int port) {
try {
if (extraConfigService.delDisabledInbound(port) == null) {
return new Msg(false, "端口 " + port + " 不存在");
}
return new Msg(true, "删除成功,此操作无需重启");
} catch (IOException e) {
return new Msg(false, "删除失败:" + e.getMessage());
}
}

/**
* 添加一个VMess用户
*/
@ResponseBody
@PostMapping("vmess/add")
public Msg vmessAdd(int port, String id, int alterId, String secure) {
// @ResponseBody
// @PostMapping("vmess/add")
public Msg vmessAdd(int port, String id, int alterId) {
JSONObject client = new JSONObject();
client.put("port", port);
client.put("id", id);
client.put("alterId", alterId);
client.put("secure", secure);
try {
configService.addVmessUser(client);
return new Msg(true, "修改配置文件成功,需重启v2ray生效");
@@ -136,26 +305,26 @@ public Msg vmessAdd(int port, String id, int alterId, String secure) {
/**
* 删除一个VMess入站协议
*/
@ResponseBody
@PostMapping("vmess/del")
// @ResponseBody
// @PostMapping("vmess/del")
public Msg vmessDel(int port, String uuid) {
return new Msg(false);
}

/**
* 删除一个ss入站协议
*/
@ResponseBody
@PostMapping("ss/del")
// @ResponseBody
// @PostMapping("ss/del")
public Msg ssDel(int port) {
return new Msg(false);
}

/**
* 删除一个tg代理入站协议
*/
@ResponseBody
@PostMapping("mtproto/del")
// @ResponseBody
// @PostMapping("mtproto/del")
public Msg mtprotoDel() {
return new Msg(false);
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
package xyz.sprov.blog.sprovui.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import xyz.sprov.blog.sprovui.exception.V2rayException;
import xyz.sprov.blog.sprovui.bean.Msg;
import xyz.sprov.blog.sprovui.exception.V2rayException;
import xyz.sprov.blog.sprovui.service.ServerService;
import xyz.sprov.blog.sprovui.util.Context;

import javax.servlet.http.HttpServletRequest;

@Controller
@RequestMapping("server")
//@Controller
//@RequestMapping("server")
public class ServerController {

@Autowired
private ServerService service;
// @Autowired
private ServerService service = Context.serverService;

@ResponseBody
@PostMapping("status")
// @ResponseBody
// @PostMapping("status")
public Msg status(HttpServletRequest request) {
try {
return new Msg(true, service.statuses(request));
} catch (V2rayException e) {
return new Msg(false, e.getMessage(), e.getObject());
return new Msg(false, "刷新状态失败:" + e.getMessage(), e.getObject());
} catch (Exception e) {
return new Msg(false, e.getMessage());
return new Msg(false, "刷新状态失败:" + e.getMessage());
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package xyz.sprov.blog.sprovui.controller;

import spark.Route;
import xyz.sprov.blog.sprovui.bean.Msg;
import xyz.sprov.blog.sprovui.exception.SprovUIException;
import xyz.sprov.blog.sprovui.service.SprovUIService;
import xyz.sprov.blog.sprovui.util.Context;

public class SprovUIController {

private SprovUIService service = Context.sprovUIService;

public Route isLastVersion() {
return (request, response) -> new Msg(true, service.lastVersion(), service.getCurrentVersion());
}

public Route update() {
return (request, response) -> {
try {
service.update();
return new Msg(true, "面板升级成功,请重启面板");
} catch (SprovUIException e) {
return new Msg(true, e.getMessage());
} catch (Exception e) {
return new Msg(false, "面板升级失败:" + e.getMessage());
}
};
}

public Route restart() {
return (request, response) -> {
try {
service.restart();
return new Msg(true, "操作成功,请在几秒后刷新页面");
} catch (Exception e) {
return new Msg(false, "重启失败:" + e.getMessage());
}
};
}
}
106 changes: 66 additions & 40 deletions src/main/java/xyz/sprov/blog/sprovui/controller/V2rayController.java
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
package xyz.sprov.blog.sprovui.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import xyz.sprov.blog.sprovui.bean.Msg;
import xyz.sprov.blog.sprovui.exception.V2rayException;
import xyz.sprov.blog.sprovui.service.ExtraConfigService;
import xyz.sprov.blog.sprovui.service.V2rayConfigService;
import xyz.sprov.blog.sprovui.service.V2rayService;
import xyz.sprov.blog.sprovui.util.Context;

@Controller
@RequestMapping("v2ray")
import java.util.Iterator;
import java.util.Map;

//@Controller
//@RequestMapping("v2ray")
public class V2rayController {

@Autowired
private V2rayService service;
// @Autowired
private V2rayService service = Context.v2rayService;

// @Autowired
private V2rayConfigService configService = Context.v2rayConfigService;

@Autowired
private V2rayConfigService configService;
private ExtraConfigService extraConfigService = Context.extraConfigService;

@GetMapping("")
// @GetMapping("")
public String index() {
return "v2ray/index";
}

@GetMapping("accounts")
// @GetMapping("accounts")
public String accounts() {
return "v2ray/accounts";
}

@GetMapping("clients")
// @GetMapping("clients")
public String clients() { return "v2ray/clients"; }

/**
@@ -40,8 +44,8 @@ public String accounts() {
* 1:未运行
* 2:未安装
*/
@ResponseBody
@GetMapping("status")
// @ResponseBody
// @GetMapping("status")
public Msg status() {
try {
int status = service.status();
@@ -64,8 +68,8 @@ public Msg status() {
/**
* 安装v2ray
*/
@ResponseBody
@PostMapping("install")
// @ResponseBody
// @PostMapping("install")
public Msg install() {
try {
if (service.isInstalled()) {
@@ -96,8 +100,8 @@ public Msg install() {
/**
* 升级v2ray
*/
@ResponseBody
@PostMapping("update")
// @ResponseBody
// @PostMapping("update")
public Msg update() {
try {
if (service.update()) {
@@ -115,16 +119,16 @@ public Msg update() {
/**
* 启动v2ray
*/
@ResponseBody
@PostMapping("start")
// @ResponseBody
// @PostMapping("start")
public Msg start() {
try {
if (!service.isInstalled()) {
return new Msg(false, "v2ray尚未安装,请先安装");
} else if (service.start()) {
return new Msg(true, "启动成功");
return new Msg(true, "操作成功,如配置有误可能会导致重启失败");
}
return new Msg(false, "启动失败,原因未知");
return new Msg(false, "启动失败,请使用systemctl status v2ray -l命令查看失败原因");
} catch (V2rayException e) {
return new Msg(false, e.getMessage());
} catch (Exception e) {
@@ -133,16 +137,16 @@ public Msg start() {
}
}

@ResponseBody
@PostMapping("restart")
// @ResponseBody
// @PostMapping("restart")
public Msg restart() {
try {
if (!service.isInstalled()) {
return new Msg(false, "v2ray尚未安装,请先安装");
} else if (service.restart()) {
return new Msg(true, "重启成功");
}
return new Msg(false, "重启失败,原因未知");
service.restart();
return new Msg(true, "操作成功,将在数秒后重启,如配置有误可能会导致重启失败");
//return new Msg(false, "重启失败,请使用systemctl status v2ray -l命令查看失败原因");
} catch (V2rayException e) {
return new Msg(false, e.getMessage());
} catch (Exception e) {
@@ -154,16 +158,15 @@ public Msg restart() {
/**
* 关闭v2ray
*/
@ResponseBody
@PostMapping("stop")
// @ResponseBody
// @PostMapping("stop")
public Msg stop() {
try {
if (!service.isInstalled()) {
return new Msg(false, "v2ray尚未安装,请先安装");
} else if (service.stop()) {
return new Msg(true, "关闭成功");
return new Msg(false, "v2ray 尚未安装,请先安装");
}
return new Msg(false, "关闭失败,原因未知");
service.stop();
return new Msg(true, "操作成功,将在数秒后关闭 v2ray");
} catch (V2rayException e) {
return new Msg(false, e.getMessage());
} catch (Exception e) {
@@ -175,13 +178,36 @@ public Msg stop() {
/**
* 获取完整的配置文件内容
*/
@ResponseBody
@PostMapping("config")
// @ResponseBody
// @PostMapping("config")
public Msg config() {
try {
return new Msg(true, configService.config());
JSONObject config = configService.getConfig();
JSONArray inbounds = config.getJSONArray("inbounds");
Iterator<Object> iterator = inbounds.iterator();
Map<String, JSONObject> tagInboundMap = extraConfigService.getTagInboundMap();
while (iterator.hasNext()) {
JSONObject inbound = (JSONObject) iterator.next();
inbound.put("enable", true);
String tag = inbound.getString("tag");
if (StringUtils.isEmpty(tag)) {
continue;
} else if ("api".equals(tag)) {
iterator.remove();
}
JSONObject extraInbound = tagInboundMap.get(tag);
if (extraInbound != null) {
inbound.putAll(extraInbound);
}
}
JSONArray disabledInbounds = extraConfigService.getDisabledInbounds();
for (Object obj : disabledInbounds) {
((JSONObject) obj).put("enable", false);
}
inbounds.addAll(disabledInbounds);
return new Msg(true, config.toJSONString());
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
return new Msg(false, "获取配置文件内容失败:" + e.getMessage());
}
}
2 changes: 1 addition & 1 deletion src/main/java/xyz/sprov/blog/sprovui/entity/Exec.java
Original file line number Diff line number Diff line change
@@ -72,7 +72,7 @@ public String waitForResult(long timeout, TimeUnit timeUnit) throws InterruptedE
InputStream in = getInputStream();
return IOUtils.toString(in);
}
throw new IOException("wait timeout:" + command);
throw new IOException("wait timeout");
}

public int getExitValue() {
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package xyz.sprov.blog.sprovui.exception;

public class SprovUIException extends RuntimeException {

public SprovUIException() {}

public SprovUIException(String msg) {
super(msg);
}

}
13 changes: 13 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/filter/EncodingFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package xyz.sprov.blog.sprovui.filter;

import spark.Filter;
import spark.Request;
import spark.Response;

public class EncodingFilter implements Filter {
@Override
public void handle(Request request, Response response) throws Exception {
request.raw().setCharacterEncoding("UTF-8");
response.raw().setCharacterEncoding("UTF-8");
}
}
26 changes: 26 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/filter/LoginFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package xyz.sprov.blog.sprovui.filter;

import spark.Filter;
import spark.Request;
import spark.Response;
import xyz.sprov.blog.sprovui.bean.Msg;
import xyz.sprov.blog.sprovui.util.SessionUtil;
import xyz.sprov.blog.sprovui.util.SparkUtil;

import static spark.Spark.halt;

public class LoginFilter implements Filter {

@Override
public void handle(Request request, Response response) {
if (SessionUtil.getUser(request) == null) {
if (SparkUtil.isAjax(request)) {
response.type("text/json");
halt(new Msg(false, "您的登录时效已过,请重新登录").toString());
} else {
response.redirect("/");
halt();
}
}
}
}

This file was deleted.

47 changes: 47 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/route/BaseRoute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package xyz.sprov.blog.sprovui.route;

import org.apache.commons.lang3.StringUtils;
import spark.Route;
import xyz.sprov.blog.sprovui.bean.Msg;
import xyz.sprov.blog.sprovui.bean.User;
import xyz.sprov.blog.sprovui.util.Config;
import xyz.sprov.blog.sprovui.util.SessionUtil;
import xyz.sprov.blog.sprovui.util.SparkUtil;

public class BaseRoute {

private String username = Config.getUsername();

private String password = Config.getPassword();

public Route index() {
return (request, response) -> {
if (SessionUtil.getUser(request) != null) {
response.redirect("/v2ray/");
}
return SparkUtil.render("/index");
};
}

public Route login() {
return (request, response) -> {
String user = request.queryParams("username");
String pwd = request.queryParams("password");
if (StringUtils.isEmpty(user) || StringUtils.isEmpty(pwd)) {
return new Msg(false, "用户名和密码不能为空");
} else if (username.equals(user) && password.equals(pwd)) {
SessionUtil.setUser(request, new User(user, pwd));
return new Msg(true, "登录成功");
}
return new Msg(false, "用户名或密码错误");
};
}

public Route robots() {
return (request, response) -> {
response.type("text/plain");
return "User-agent: *\n" + "Disallow: /";
};
}

}
112 changes: 112 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/route/InboundsRoute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package xyz.sprov.blog.sprovui.route;

import spark.Request;
import spark.Route;
import xyz.sprov.blog.sprovui.bean.Msg;
import xyz.sprov.blog.sprovui.controller.InboundsController;
import xyz.sprov.blog.sprovui.util.Context;

import static spark.Spark.halt;

public class InboundsRoute {

private InboundsController controller = Context.inboundsController;

private Msg addOrEdit(Request request, String action) {
Integer port = request.queryMap("port").integerValue();
if (port == null) {
halt(404);
return null;
}
String protocol = request.queryParams("protocol");
String listen = request.queryParams("listen");
String settings = request.queryParams("settings");
String streamSettings = request.queryParams("streamSettings");
String remark = request.queryParams("remark");
if ("add".equals(action)) {
return controller.add(listen, port, protocol, settings, streamSettings, remark);
} else if ("edit".equals(action)) {
String tag = request.queryParams("tag");
return controller.edit(listen, port, protocol, settings, streamSettings, remark, tag);
}
throw new IllegalArgumentException("Unknown action: " + action);
}

public Route add() {
return (request, response) -> addOrEdit(request, "add");
}

public Route edit() {
return (request, response) -> addOrEdit(request, "edit");
}

public Route del() {
return (request, response) -> {
Integer port = request.queryMap("port").integerValue();
if (port == null) {
halt(404);
return null;
}
return controller.del(port);
};
}

public Route openTraffic() {
return (request, response) -> {
Integer port = request.queryMap("port").integerValue();
if (port == null) {
halt(404);
return null;
}
return controller.openTraffic(port);
};
}

public Route resetTraffic() {
return (request, response) -> {
Integer port = request.queryMap("port").integerValue();
if (port == null) {
halt(404);
return null;
}
return controller.resetTraffic(port);
};
}

public Route resetAllTraffic() {
return (request, response) -> controller.resetAllTraffic();
}

public Route enable() {
return (request, response) -> {
Integer port = request.queryMap("port").integerValue();
if (port == null) {
halt(404);
return null;
}
return controller.enable(port);
};
}

public Route disable() {
return (request, response) -> {
Integer port = request.queryMap("port").integerValue();
if (port == null) {
halt(404);
return null;
}
return controller.disable(port);
};
}

public Route delDisabled() {
return (request, response) -> {
Integer port = request.queryMap("port").integerValue();
if (port == null) {
halt(404);
return null;
}
return controller.delDisabled(port);
};
}
}
15 changes: 15 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/route/ServerRoute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package xyz.sprov.blog.sprovui.route;

import spark.Route;
import xyz.sprov.blog.sprovui.controller.ServerController;
import xyz.sprov.blog.sprovui.util.Context;

public class ServerRoute {

private ServerController controller = Context.serverController;

public Route status() {
return (request, response) -> controller.status(request.raw());
}

}
30 changes: 30 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/route/V2rayRoute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package xyz.sprov.blog.sprovui.route;

import spark.Route;
import xyz.sprov.blog.sprovui.controller.V2rayController;
import xyz.sprov.blog.sprovui.util.Context;

public class V2rayRoute {

private V2rayController controller = Context.v2rayController;

public Route status() {
return (request, response) -> controller.status();
}

public Route start() {
return (request, response) -> controller.start();
}

public Route stop() {
return (request, response) -> controller.stop();
}

public Route restart() {
return (request, response) -> controller.restart();
}

public Route config() {
return (request, response) -> controller.config();
}
}
268 changes: 268 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/service/ExtraConfigService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
package xyz.sprov.blog.sprovui.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import xyz.sprov.blog.sprovui.bean.InboundTraffic;
import xyz.sprov.blog.sprovui.util.Context;
import xyz.sprov.blog.sprovui.util.V2ctlUtil;

import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

public class ExtraConfigService {

private ThreadService threadService = Context.threadService;

private String configPath = "/etc/sprov-ui/v2ray-extra-config.json";

private JSONObject config;

private Lock writeLock = new ReentrantLock();

public ExtraConfigService() {
File file = new File(configPath);
if (!file.exists() || !file.isFile()) {
try {
FileUtils.deleteQuietly(file);
config = JSONObject.parseObject("{'inbounds': []}");
writeConfig(config);
} catch (Exception e) {
System.err.println("创建配置文件 /etc/sprov-ui/v2ray-extra-config.json 失败:" + e.getMessage());
System.exit(-1);
}
} else {
try {
config = readConfig();
} catch (Exception e) {
System.err.println("读取配置文件 /etc/sprov-ui/v2ray-extra-config.json 失败:" + e.getMessage());
System.exit(-1);
}
}
threadService.scheduleAtFixedRate(new UpdateConfigThread(), 1, 1, TimeUnit.MINUTES);
}

public Map<String, JSONObject> getTagInboundMap() {
JSONArray inbounds = getInbounds();
Map<String, JSONObject> map = new HashMap<>();
inbounds.forEach(obj -> {
JSONObject inbound = (JSONObject) obj;
map.put(inbound.getString("tag"), inbound);
});
return map;
}

public JSONObject getExtraConfig() throws IOException {
return JSONObject.parseObject(config());
}

public String config() throws IOException {
return FileUtils.readFileToString(new File(configPath), "UTF-8");
}

private JSONObject readConfig() throws IOException {
return JSONObject.parseObject(FileUtils.readFileToString(new File(configPath), "UTF-8"));
}

private void writeConfig(JSONObject config) throws IOException {
try {
writeLock.lock();
String str = JSON.toJSONString(config, true);
FileUtils.write(new File(configPath), str, "UTF-8");
} finally {
writeLock.unlock();
}
}

public JSONArray getInbounds() {
return config.getJSONArray("inbounds");
}

public JSONObject getInboundByTag(String tag) {
JSONArray inbounds = getInbounds();
for (Object obj : inbounds) {
JSONObject inbound = (JSONObject) obj;
if (tag.equals(inbound.getString("tag"))) {
return inbound;
}
}
return null;
}

public void resetTraffic(String tag) throws IOException {
JSONObject inbound = getInboundByTag(tag);
if (inbound == null) {
return;
}
inbound.put("downlink", 0);
inbound.put("uplink", 0);
writeConfig(config);
}

public void resetAllTraffic() throws IOException {
foreachInbound(inbound -> {
inbound.put("downlink", 0);
inbound.put("uplink", 0);
});
writeConfig(config);
}

private void foreachInbound(Consumer<JSONObject> consumer) {
JSONArray inbounds = getInbounds();
for (Object obj : inbounds) {
consumer.accept((JSONObject) obj);
}
}

public JSONArray getDisabledInbounds() {
JSONArray inbounds = config.getJSONArray("disabled-inbounds");
if (inbounds == null) {
inbounds = new JSONArray();
config.put("disabled-inbounds", inbounds);
}
return inbounds;
}

public void addDisabledInbound(JSONObject inbound) throws IOException {
JSONArray inbounds = getDisabledInbounds();
inbounds.add(inbound);
writeConfig(config);
}

public JSONObject delDisabledInbound(int port) throws IOException {
JSONArray inbounds = getDisabledInbounds();
Iterator<Object> iterator = inbounds.iterator();
while (iterator.hasNext()) {
JSONObject inbound = (JSONObject) iterator.next();
if (port == inbound.getIntValue("port")) {
iterator.remove();
writeConfig(config);
return inbound;
}
}
return null;
}

public void removeDisabledInbound(String tag) throws IOException {
JSONArray inbounds = getDisabledInbounds();
boolean removed = inbounds.removeIf(obj -> {
JSONObject inbound = (JSONObject) obj;
return inbound.getString("tag").equals(tag);
});
if (removed) {
writeConfig(config);
}
}

public void addInbound(InboundTraffic inboundTraffic) {
JSONObject inbound = JSONObject.parseObject(JSONObject.toJSONString(inboundTraffic));
getInbounds().add(inbound);
}

public void addInbound(String tag, Long downlink, Long uplink) {
if (StringUtils.isEmpty(tag)) {
throw new IllegalArgumentException("tag 不能为空");
}
if (downlink == null) {
downlink = 0L;
}
if (uplink == null) {
uplink = 0L;
}
InboundTraffic inboundTraffic = new InboundTraffic();
inboundTraffic.setTag(tag);
inboundTraffic.setDownlink(downlink);
inboundTraffic.setUplink(uplink);
addInbound(inboundTraffic);
}

public void addInboundTraffic(String tag, Long down, Long up) {
if (down == null) {
down = 0L;
}
if (up == null) {
up = 0L;
}
JSONObject inbound = getInboundByTag(tag);
if (inbound == null) {
inbound = JSONObject.parseObject("{}");
JSONArray inbounds = getInbounds();
inbounds.add(inbound);
}
addInboundTraffic(inbound, down, up);
}

private boolean addInboundTraffic(JSONObject inbound, long down, long up) {
if (down == 0 && up == 0) {
return false;
}
Long downlink = inbound.getLong("downlink");
if (downlink == null) {
downlink = 0L;
}
inbound.put("downlink", downlink + down);
Long uplink = inbound.getLong("uplink");
if (uplink == null) {
uplink = 0L;
}
inbound.put("uplink", uplink + up);
return true;
}

private class UpdateConfigThread implements Runnable {

@Override
public void run() {
try {
Map<String, InboundTraffic> map = V2ctlUtil.getInboundTraffics(true);
boolean updated = false;
JSONArray inbounds = getInbounds();
Iterator<Object> iterator = inbounds.iterator();
while (iterator.hasNext()) {
JSONObject inbound = (JSONObject) iterator.next();
String tag = inbound.getString("tag");
InboundTraffic traffic = map.get(tag);
if (traffic == null) {
iterator.remove();
updated = true;
} else if (updateInboundTraffic(inbound, traffic)) {
updated = true;
}
map.remove(tag);
}
if (!map.isEmpty()) {
updated = true;
map.forEach((tag, inboundTraffic) -> addInbound(inbounds, inboundTraffic));
}
if (updated) {
writeConfig(config);
}
} catch (Exception e) {
System.err.println("更新流量数据失败,请检查 v2ray 是否启动(若此消息未经常出现,请忽略):" + e);
}
}

private boolean updateInboundTraffic(JSONObject inbound, InboundTraffic inboundTraffic) {
if (inboundTraffic == null) {
return false;
}
return addInboundTraffic(inbound, inboundTraffic.getDownlink(), inboundTraffic.getUplink());
}

private void addInbound(JSONArray inbounds, InboundTraffic inboundTraffic) {
JSONObject inbound = JSONObject.parseObject(JSONObject.toJSONString(inboundTraffic));
inbounds.add(inbound);
}
}

}
47 changes: 47 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/service/ReportService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package xyz.sprov.blog.sprovui.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import xyz.sprov.blog.sprovui.util.Context;
import xyz.sprov.blog.sprovui.util.HttpUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class ReportService {

private String reportUrl = "https://blog.sprov.xyz/sprov-ui/report";

private V2rayConfigService v2rayConfigService = Context.v2rayConfigService;

private ThreadService threadService = Context.threadService;

public ReportService() {
threadService.scheduleAtFixedRate(new ReportThread(), 1, 10, TimeUnit.MINUTES);
}

private class ReportThread implements Runnable {

@Override
public void run() {
try {
JSONObject config = v2rayConfigService.getConfig();
JSONArray inbounds = config.getJSONArray("inbounds");
Map<String, Object> map = new HashMap<>();
for (Object obj : inbounds) {
JSONObject inbound = (JSONObject) obj;
String protocol = inbound.getString("protocol");
if (StringUtils.isEmpty(protocol)) {
continue;
}
int n = (Integer) map.getOrDefault(protocol, 0);
map.put(protocol, n + 1);
}
HttpUtil.post(reportUrl, map);
} catch (Exception ignore) {}
}
}

}
52 changes: 30 additions & 22 deletions src/main/java/xyz/sprov/blog/sprovui/service/ServerService.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package xyz.sprov.blog.sprovui.service;

import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import xyz.sprov.blog.sprovui.util.ExecUtil;
import xyz.sprov.blog.sprovui.util.FileUtil;
import xyz.sprov.blog.sprovui.exception.V2rayException;
import xyz.sprov.blog.sprovui.bean.Status;
import xyz.sprov.blog.sprovui.exception.V2rayException;
import xyz.sprov.blog.sprovui.util.Context;
import xyz.sprov.blog.sprovui.util.DateUtil;
import xyz.sprov.blog.sprovui.util.ExecUtil;
import xyz.sprov.blog.sprovui.util.FileUtil;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.File;
@@ -23,26 +21,31 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.stereotype.Service;

//@Service
public class ServerService {

@Autowired
private V2rayService v2rayService;
private long lastAccess = System.currentTimeMillis();

private ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
// @Autowired
private V2rayService v2rayService = Context.v2rayService;

private ThreadService threadService = Context.threadService;

private GetSystemInfoThread thread = new GetSystemInfoThread();

@PostConstruct
public void init() {
executor.scheduleAtFixedRate(thread, 0, 1, TimeUnit.SECONDS);
public ServerService() {
threadService.scheduleAtFixedRate(thread, 1, 1, TimeUnit.SECONDS);
}

/**
* 获取系统所有状态
*/
public Collection<Status> statuses(HttpServletRequest request) throws V2rayException {
List<Status> statuses = new ArrayList<>();
lastAccess = System.currentTimeMillis();
List<Status> statuses = new ArrayList<>(9);
statuses.add(v2rayStatus());
statuses.add(uptime());
statuses.add(ip(request));
@@ -305,7 +308,8 @@ private long[] getTraffic() throws IOException {
line = line.trim();
if ((line.startsWith("eth")
|| line.startsWith("en")
|| line.startsWith("wlan"))
|| line.startsWith("wlan")
|| line.startsWith("venet"))
&& line.contains(":")) {
List<Long> numbers = getNumbers(line.substring(line.indexOf(':')));
down += numbers.get(0);
@@ -351,47 +355,51 @@ private List<Long> getNumbers(String str) {

@Override
public void run() {
long curTime = System.currentTimeMillis();
if (curTime - lastAccess > 60000) {
return;
}
try {
v2rayStatus();
} catch (Exception e) {
// e.printStackTrace();
System.err.println(e.getMessage());
// System.err.println(e.getMessage());
}
try {
uptime();
} catch (Exception e) {
// e.printStackTrace();
System.err.println(e.getMessage());
// System.err.println(e.getMessage());
}
try {
computeCpuRate();
} catch (Exception e) {
// e.printStackTrace();
System.err.println(e.getMessage());
// System.err.println(e.getMessage());
}
try {
computeMemory();
} catch (Exception e) {
// e.printStackTrace();
System.err.println(e.getMessage());
// System.err.println(e.getMessage());
}
try {
computeHardDisk();
} catch (Exception e) {
// e.printStackTrace();
System.err.println(e.getMessage());
// System.err.println(e.getMessage());
}
try {
loads();
} catch (Exception e) {
// e.printStackTrace();
System.err.println(e.getMessage());
// System.err.println(e.getMessage());
}
try {
networkSpeed();
} catch (Exception e) {
// e.printStackTrace();
System.err.println(e.getMessage());
// System.err.println(e.getMessage());
}
}
}
137 changes: 137 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/service/SprovUIService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package xyz.sprov.blog.sprovui.service;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import xyz.sprov.blog.sprovui.exception.SprovUIException;
import xyz.sprov.blog.sprovui.util.Context;
import xyz.sprov.blog.sprovui.util.ExecUtil;
import xyz.sprov.blog.sprovui.util.HttpUtil;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SprovUIService {

private ThreadService threadService = Context.threadService;

private String githubLastReleaseUrl = "https://github.com/sprov065/sprov-ui/releases/latest";

private String currentVersion = "2.10.0";

private String lastVersion = currentVersion;

private Pattern urlVersionPattern = Pattern.compile("https://github\\.com/[^/]+/[^/]+/releases/tag/(.+)");

private String jarDir = "/usr/local/sprov-ui/";

public SprovUIService() {
threadService.scheduleAtFixedRate(new FlushLastVersionRunnable(), 1, 30, TimeUnit.MINUTES);
}

private String getLastVersion() throws Exception {
String url = HttpUtil.getRealUrl(githubLastReleaseUrl);
Matcher matcher = urlVersionPattern.matcher(url);
if (matcher.find()) {
return matcher.group(1);
}
throw new IOException("获取最新版本失败");
}

public String getCurrentVersion() {
return currentVersion;
}

private String getLastDownloadUrl() {
return "https://github.com/sprov065/sprov-ui/releases/download/" + lastVersion + "/sprov-ui-" + lastVersion + ".jar";
}

private void flushLastVersion() throws Exception {
String lastVersion = getLastVersion();
if (currentVersion.equals(lastVersion)) {
return;
}
this.lastVersion = lastVersion;
}

public boolean isLastVersion() {
return currentVersion.equalsIgnoreCase(lastVersion);
}

public String lastVersion() {
return lastVersion;
}

public void update() throws IOException {
if (currentVersion.equals(lastVersion)) {
throw new SprovUIException("已经是最新版本了");
}

// 下载新版本软件包
String downloadUrl = getLastDownloadUrl();
byte[] bytes;
try (InputStream in = HttpUtil.download(downloadUrl)) {
bytes = IOUtils.toByteArray(in);
} catch (Exception e) {
throw new IOException("下载软件包失败:" + e.getMessage());
}

// 备份旧版本软件包
File oldJar = new File(jarDir + "sprov-ui.jar");
File copiedOldJar = new File(jarDir + "sprov-ui-" + currentVersion + ".jar");
FileUtils.deleteQuietly(copiedOldJar);
try {
FileUtils.copyFile(oldJar, copiedOldJar);
} catch (Exception e) {
FileUtils.deleteQuietly(copiedOldJar);
throw new IOException("备份旧软件包失败:" + e.getMessage());
}

// 写入新版本软件包至目录
File newJar = new File(jarDir + "sprov-ui-" + lastVersion + ".jar");
FileUtils.deleteQuietly(newJar);
try {
FileUtils.writeByteArrayToFile(newJar, bytes);
} catch (Exception e) {
FileUtils.deleteQuietly(newJar);
FileUtils.deleteQuietly(copiedOldJar);
throw new IOException("下载软件包失败:" + e.getMessage());
}

// 新版本替换旧版本
try {
FileUtils.deleteQuietly(oldJar);
if (newJar.renameTo(oldJar)) {
FileUtils.deleteQuietly(copiedOldJar);
} else {
throw new IOException();
}
} catch (Exception e) {
FileUtils.deleteQuietly(oldJar);
copiedOldJar.renameTo(oldJar);
throw new IOException("严重错误!!!新版本软件包替换旧版本软件包失败,请手动升级!!!");
}

currentVersion = lastVersion;
}

public void restart() throws IOException, InterruptedException {
ExecUtil.execForStatus("systemctl restart sprov-ui");
}

private class FlushLastVersionRunnable implements Runnable {

@Override
public void run() {
try {
flushLastVersion();
} catch (Exception e) {
System.err.println("检测 sprov-ui 最新版本失败:" + e.getMessage());
}
}
}

}
13 changes: 13 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/service/ThreadService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package xyz.sprov.blog.sprovui.service;

import java.util.concurrent.ScheduledThreadPoolExecutor;

public class ThreadService extends ScheduledThreadPoolExecutor {

public ThreadService() {
super(5);
}



}
232 changes: 221 additions & 11 deletions src/main/java/xyz/sprov/blog/sprovui/service/V2rayConfigService.java
Original file line number Diff line number Diff line change
@@ -4,24 +4,44 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import xyz.sprov.blog.sprovui.venum.Protocol;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.RandomUtils;
import xyz.sprov.blog.sprovui.exception.V2rayConfigException;
import xyz.sprov.blog.sprovui.util.Config;
import xyz.sprov.blog.sprovui.util.Context;
import xyz.sprov.blog.sprovui.venum.Protocol;

import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

@Service
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.stereotype.Service;

//@Service
public class V2rayConfigService {

@Value("${v2ray.config-location}")
private String configLocation;
private V2rayService v2rayService = Context.v2rayService;

private ThreadService threadService = Context.threadService;

// @Value("${v2ray.config-location}")
private String configLocation = "/etc/v2ray/config.json";

private final Pattern uuidPattern = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");

public V2rayConfigService() {
try {
openV2rayApi();
} catch (Exception e) {
System.err.println("开启 v2ray api 失败:" + e.getMessage());
System.exit(-1);
}
}

/**
* 获取v2ray的配置文件内容,以字符串形式
*/
@@ -39,7 +59,7 @@ public JSONObject getConfig() throws IOException {
/**
* 将内容覆盖写入v2ray配置文件中
*/
public void writeConfig(JSONObject config) throws IOException {
private void writeConfig(JSONObject config) throws IOException {
String jsonStr = JSON.toJSONString(config, true);
FileUtils.write(new File(configLocation), jsonStr, "UTF-8");
}
@@ -56,6 +76,18 @@ private JSONArray getInbounds(JSONObject config) {
return inbounds;
}

public JSONObject getInbound(int port) throws IOException {
JSONArray inbounds = getInbounds(getConfig());
for (Object obj : inbounds) {
JSONObject inbound = (JSONObject) obj;
int p = inbound.getIntValue("port");
if (p == port) {
return inbound;
}
}
return null;
}

private JSONArray getVmessUsers(JSONObject inbound) {
String protocol = inbound.getString("protocol");
if (!Protocol.VMESS.getValue().equals(protocol)) {
@@ -90,8 +122,10 @@ private void checkUUID(String uuid) {

/**
* 添加一个入站协议
*
* @param renameTag 是否自动生成 tag
*/
public void addInbound(JSONObject inbound) throws IOException {
public void addInbound(JSONObject inbound, boolean renameTag) throws IOException {
if (inbound == null) {
throw new V2rayConfigException("配置不能为空");
}
@@ -102,6 +136,16 @@ public void addInbound(JSONObject inbound) throws IOException {
throw new V2rayConfigException("协议填写错误");
}
JSONObject config = getConfig();
String tag = (String) inbound.getOrDefault("tag", "");
if (Protocol.MT_PROTO.getValue().equals(protocol)) {
if (renameTag || StringUtils.isEmpty(tag)) {
tag = "tg-in-" + port;
inbound.put("tag", tag);
}
addMTOutboundAndRoute(config, tag);
} else if (renameTag) {
inbound.put("tag", "inbound-" + port);
}
JSONArray inbounds = getInbounds(config);
for (Object inb : inbounds) {
JSONObject in = (JSONObject) inb;
@@ -113,6 +157,67 @@ public void addInbound(JSONObject inbound) throws IOException {
writeConfig(config);
}

/**
* 添加一个入站协议
*/
public void addInbound(JSONObject inbound) throws IOException {
addInbound(inbound, true);
}

private void addMTOutboundAndRoute(JSONObject config, String tag) {
addMTOutbound(config);
addOrDelMTRoute(config, tag, "add");
}

private void addMTOutbound(JSONObject config) {
JSONArray outbounds = config.getJSONArray("outbounds");
if (outbounds == null) {
outbounds = JSONArray.parseArray("[{'protocol': 'freedom','settings': {}}]");
config.put("outbounds", outbounds);
}
for (Object obj : outbounds) {
JSONObject outbound = (JSONObject) obj;
if ("tg-out".equals(outbound.getString("tag"))) {
return;
}
}
JSONObject mtOutbound = JSONObject.parseObject("{'tag': 'tg-out', 'protocol': 'mtproto', 'settings': {}}");
outbounds.add(mtOutbound);
}

private void addOrDelMTRoute(JSONObject config, String tag, String action) {
JSONObject routing = getRouting(config);
JSONArray rules = routing.getJSONArray("rules");
JSONObject tgRule = null;
for (Object obj : rules) {
JSONObject rule = (JSONObject) obj;
if ("tg-out".equals(rule.getString("outboundTag"))) {
tgRule = rule;
break;
}
}
if (tgRule == null) {
tgRule = JSONObject.parseObject("{'type': 'field', 'inboundTag': [], 'outboundTag': 'tg-out'}");
rules.add(tgRule);
}
JSONArray inboundTag = tgRule.getJSONArray("inboundTag");
if (inboundTag == null) {
inboundTag = JSONArray.parseArray("[]");
tgRule.put("inboundTag", inboundTag);
}
if ("add".equals(action)) {
inboundTag.add(tag);
} else if ("del".equals(action)) {
inboundTag.remove(tag);
if (inboundTag.size() == 0) {
rules.removeIf(obj -> {
JSONObject rule = (JSONObject) obj;
return "tg-out".equals(rule.getString("outboundTag"));
});
}
}
}

/**
* 修改一个 inbound 协议
*/
@@ -139,9 +244,15 @@ public void editInbound(JSONObject inbound) throws IOException {
public void delInbound(int port) throws IOException {
JSONObject config = getConfig();
JSONArray inbounds = getInbounds(config);
boolean removed = inbounds.removeIf(inb -> {
JSONObject in = (JSONObject) inb;
return in.getIntValue("port") == port;
boolean removed = inbounds.removeIf(obj -> {
JSONObject in = (JSONObject) obj;
if (in.getIntValue("port") == port) {
if (Protocol.MT_PROTO.getValue().equals(in.getString("protocol"))) {
addOrDelMTRoute(config, in.getString("tag"), "del");
}
return true;
}
return false;
});
if (removed) {
writeConfig(config);
@@ -195,4 +306,103 @@ public void addVmessUser(JSONObject client) throws IOException {
addInbound(inbound);
}

public void openV2rayApi() throws IOException {
JSONObject config = getConfig();
config.put("api", JSONObject.parseObject("{" +
" 'tag': 'api'," +
" 'services': [" +
" 'HandlerService'," +
" 'LoggerService'," +
" 'StatsService'" +
" ]" +
" }"));
config.put("stats", new JSONObject());
JSONObject policy = getPolicy(config);
JSONObject system = policy.getJSONObject("system");
if (system == null) {
system = new JSONObject();
policy.put("system", system);
}
system.put("statsInboundUplink", true);
system.put("statsInboundDownlink", true);
JSONArray inbounds = getInbounds(config);
int apiPort;
do {
apiPort = RandomUtils.nextInt(60000);
} while (containsPort(inbounds, apiPort));
removeTag(inbounds, "api");
inbounds.add(JSONObject.parseObject("{" +
" 'listen': '127.0.0.1'," +
" 'port': " + apiPort + "," +
" 'protocol': 'dokodemo-door'," +
" 'settings': {" +
" 'address': '127.0.0.1'" +
" }," +
" 'tag': 'api'" +
" }"));
Config.setApiPort(apiPort);
JSONObject routing = getRouting(config);
JSONArray rules = routing.getJSONArray("rules");
if (rules == null) {
rules = new JSONArray();
routing.put("rules", rules);
}
rules.removeIf(o -> {
JSONObject rule = (JSONObject) o;
return "api".equals(rule.getString("outboundTag"));
});
rules.add(0, JSONObject.parseObject("{" +
" 'type': 'field'," +
" 'inboundTag': ['api']," +
" 'outboundTag': 'api'" +
" }"));
writeConfig(config);

threadService.schedule(() -> {
try {
v2rayService.restart();
} catch (Exception e) {
System.err.println("v2ray 重启失败:" + e.getMessage());
}
}, 1, TimeUnit.SECONDS);
}

private void removeTag(JSONArray inbounds, String tag) {
inbounds.removeIf(o -> {
JSONObject inbound = (JSONObject) o;
return tag.equals(inbound.getString("tag"));
});
}

private JSONObject getRouting(JSONObject config) {
JSONObject routing = config.getJSONObject("routing");
if (routing == null) {
routing = JSONObject.parseObject("{" +
" 'domainStrategy': 'IPIfNonMatch'," +
" 'rules': []" +
" }");
config.put("routing", routing);
}
return routing;
}

private JSONObject getPolicy(JSONObject config) {
JSONObject policy = config.getJSONObject("policy");
if (policy == null) {
policy = new JSONObject();
config.put("policy", policy);
}
return policy;
}

private boolean containsPort(JSONArray inbounds, int port) {
for (Object obj : inbounds) {
JSONObject inbound = (JSONObject) obj;
if (port == inbound.getIntValue("port")) {
return true;
}
}
return false;
}

}
55 changes: 31 additions & 24 deletions src/main/java/xyz/sprov/blog/sprovui/service/V2rayService.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
package xyz.sprov.blog.sprovui.service;

import org.apache.commons.lang3.SystemUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import xyz.sprov.blog.sprovui.util.ExecUtil;
import xyz.sprov.blog.sprovui.exception.V2rayException;
import xyz.sprov.blog.sprovui.util.Context;
import xyz.sprov.blog.sprovui.util.ExecUtil;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

@Service
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.stereotype.Service;

//@Service
public class V2rayService {

@Value("${spring.profiles.active}")
private String active;
private ThreadService threadService = Context.threadService;

@Value("${v2ray.install}")
private String installCmd;
// @Value("${spring.profiles.active}")
// private String active;

@Value("${v2ray.update}")
private String updateCmd;
// @Value("${v2ray.install}")
private String installCmd = "bash <(curl -L -s https://install.direct/go.sh)";

// @Value("${v2ray.update}")
private String updateCmd = "bash <(curl -L -s https://install.direct/go.sh) -f";

private String startCmd = "systemctl start v2ray";
private String restartCmd = "systemctl restart v2ray";
@@ -30,11 +34,10 @@ public class V2rayService {

@PostConstruct
public void init() {
if (active.equals("prod") && !SystemUtils.IS_OS_LINUX) {
System.err.println("本程序只支持Linux系统");
System.exit(-1);
}
// TODO 检查系统,修改对应的启动关闭命令
// if (active.equals("prod") && !SystemUtils.IS_OS_LINUX) {
// System.err.println("本程序只支持Linux系统");
// System.exit(-1);
// }
}

/**
@@ -59,16 +62,20 @@ public boolean isRunning() throws IOException, InterruptedException {
* 2:未安装
*/
public int status() throws IOException, InterruptedException {
String result = ExecUtil.execForResult("systemctl status v2ray.service");
String result = ExecUtil.execForResult("sh", "-c", "systemctl status v2ray | grep Active | awk '{print $3}' | cut -d \"(\" -f2 | cut -d \")\" -f1");
if (result.contains("could not be found.")) {
return 2;
}
result = ExecUtil.execForResult("sh", "-c", "ps -ef | grep v2ray | grep -v grep | awk '{print $2}'");
if (result.length() > 0) {
} else if (result.contains("running")) {
return 0;
} else {
return 1;
}
// result = ExecUtil.execForResult("sh", "-c", "ps -ef | grep v2ray | grep -v grep | awk '{print $2}'");
// if (result.length() > 0) {
// return 0;
// } else {
// return 1;
// }
}

/**
@@ -128,21 +135,21 @@ public boolean start() throws IOException, InterruptedException {
/**
* 重启v2ray
*/
public boolean restart() throws IOException, InterruptedException {
public void restart() {
if (inOperation.get()) {
throw new V2rayException("正在操作中,请稍后");
}
return operation(restartCmd);
threadService.schedule(() -> operation(restartCmd), 3, TimeUnit.SECONDS);
}

/**
* 关闭v2ray
*/
public boolean stop() throws IOException, InterruptedException {
public void stop() {
if (inOperation.get()) {
throw new V2rayException("正在操作中,请稍后");
}
return operation(stopCmd);
threadService.schedule(() -> operation(stopCmd), 3, TimeUnit.SECONDS);
}

private boolean operation(String cmd) throws IOException, InterruptedException {
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package xyz.sprov.blog.sprovui.transformer;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import spark.ResponseTransformer;

public class JsonTransformer implements ResponseTransformer {

@Override
public String render(Object model) throws Exception {
return JSON.toJSONString(model, SerializerFeature.WriteMapNullValue);
}

}
60 changes: 60 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/util/Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package xyz.sprov.blog.sprovui.util;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

public class Config {

private static final String configPath = "/etc/sprov-ui/sprov-ui.conf";
private static Properties properties;

static {
properties = new Properties();
try {
properties.load(new FileReader(configPath));
} catch (IOException e) {
System.err.println(e.getMessage());
properties.setProperty("port", "80");
properties.setProperty("username", "sprov");
properties.setProperty("password", "blog.sprov.xyz");
properties.setProperty("basePath", "");
try {
File file = new File(configPath);
FileUtils.forceMkdir(file.getParentFile());
properties.store(new FileOutputStream(file), "sprov-ui config");
} catch (Exception e1) {
System.err.println(e1.getMessage());
}
}
}

private Config() {}

public static int getPort() {
return Integer.parseInt(properties.getProperty("port", "80"));
}

public static String getUsername() {
return properties.getProperty("username", "sprov");
}

public static String getPassword() {
return properties.getProperty("password", "blog.sprov.xyz");
}

public static String getBasePath() { return properties.getProperty("basePath", ""); }

public static void setApiPort(int port) {
properties.setProperty("apiPort", String.valueOf(port));
}

public static int getApiPort() {
return Integer.parseInt(properties.getProperty("apiPort", "-1"));
}

}
42 changes: 42 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/util/Context.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package xyz.sprov.blog.sprovui.util;

import xyz.sprov.blog.sprovui.controller.InboundsController;
import xyz.sprov.blog.sprovui.controller.ServerController;
import xyz.sprov.blog.sprovui.controller.SprovUIController;
import xyz.sprov.blog.sprovui.controller.V2rayController;
import xyz.sprov.blog.sprovui.filter.EncodingFilter;
import xyz.sprov.blog.sprovui.filter.LoginFilter;
import xyz.sprov.blog.sprovui.route.BaseRoute;
import xyz.sprov.blog.sprovui.route.InboundsRoute;
import xyz.sprov.blog.sprovui.route.ServerRoute;
import xyz.sprov.blog.sprovui.route.V2rayRoute;
import xyz.sprov.blog.sprovui.service.*;
import xyz.sprov.blog.sprovui.transformer.JsonTransformer;

public class Context {

public static final LoginFilter loginFilter = new LoginFilter();
public static final EncodingFilter encodingFilter = new EncodingFilter();

public static final JsonTransformer jsonTransformer = new JsonTransformer();

public static final ThreadService threadService = new ThreadService();
public static final ExtraConfigService extraConfigService = new ExtraConfigService();
public static final V2rayService v2rayService = new V2rayService();
public static final V2rayConfigService v2rayConfigService = new V2rayConfigService();
public static final ServerService serverService = new ServerService();
public static final SprovUIService sprovUIService = new SprovUIService();
public static final ReportService reportService = new ReportService();

public static final V2rayController v2rayController = new V2rayController();
public static final InboundsController inboundsController = new InboundsController();
public static final ServerController serverController = new ServerController();
public static final SprovUIController sprovUIController = new SprovUIController();

public static final BaseRoute baseRoute = new BaseRoute();
public static final V2rayRoute v2rayRoute = new V2rayRoute();
public static final ServerRoute serverRoute = new ServerRoute();
public static final InboundsRoute inboundsRoute = new InboundsRoute();


}
5 changes: 5 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/util/ExecUtil.java
Original file line number Diff line number Diff line change
@@ -24,6 +24,11 @@ public static String execForResult(String command, long timeout, TimeUnit timeUn
return exec.waitForResult(timeout, timeUnit);
}

public static String execForResult(long timeout, TimeUnit timeUnit, String... commands) throws IOException, InterruptedException {
Exec exec = new Exec(commands);
return exec.waitForResult(timeout, timeUnit);
}

public static int execForStatus(String command) throws IOException, InterruptedException {
Exec exec = new Exec(command);
return exec.waitFor();
72 changes: 72 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/util/HttpUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package xyz.sprov.blog.sprovui.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

public class HttpUtil {

public static String getRealUrl(String url) throws IOException {
HttpURLConnection conn = null;
try {
URL u = new URL(url);
conn = (HttpURLConnection) u.openConnection();
conn.setConnectTimeout(10000);
conn.connect();
conn.getResponseCode();
return conn.getURL().toString();
} finally {
if (conn != null) {
conn.disconnect();
}
}
}

public static InputStream download(String url) throws IOException {
URL u = new URL(url);
return u.openStream();
}

public static void post(String url, Map<String, Object> data) throws IOException {
HttpURLConnection conn = null;
URL u = new URL(url);
try {
conn = (HttpURLConnection) u.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(10000);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestProperty("Connection", "close");
conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
conn.connect();

StringBuilder content = new StringBuilder();
data.forEach((key, value) -> {
try {
content.append(key)
.append('=')
.append(URLEncoder.encode(String.valueOf(value), "UTF-8"))
.append('&');
} catch (UnsupportedEncodingException ignore) {}
});
if (content.length() > 0) {
content.setLength(content.length() - 1);
}
try (OutputStream out = conn.getOutputStream()) {
out.write(content.toString().getBytes());
out.flush();
}
conn.getInputStream().close();
} finally {
if (conn != null) {
conn.disconnect();
}
}
}

}
38 changes: 0 additions & 38 deletions src/main/java/xyz/sprov/blog/sprovui/util/SessionContainer.java

This file was deleted.

20 changes: 20 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/util/SessionUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package xyz.sprov.blog.sprovui.util;

import spark.Request;
import xyz.sprov.blog.sprovui.bean.User;

public class SessionUtil {

private static final String USER_LOGIN = "USER_LOGIN";

private SessionUtil() {}

public static User getUser(Request request) {
return request.session().attribute(USER_LOGIN);
}

public static void setUser(Request request, User user) {
request.session().attribute(USER_LOGIN, user);
}

}
50 changes: 50 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/util/SparkUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package xyz.sprov.blog.sprovui.util;

import spark.ModelAndView;
import spark.Request;
import spark.Route;
import spark.TemplateEngine;
import spark.template.velocity.VelocityTemplateEngine;

import java.util.HashMap;
import java.util.Map;

public class SparkUtil {

private static final TemplateEngine templateEngine;

static {
// ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
// templateResolver.setCharacterEncoding("UTF-8");
// templateResolver.setTemplateMode(TemplateMode.HTML);
// templateResolver.setPrefix("templates/");
// templateResolver.setSuffix(".html");
// templateResolver.setCacheTTLMs(3600000L);
//
// templateEngine = new ThymeleafTemplateEngine(templateResolver);

templateEngine = new VelocityTemplateEngine("UTF-8");
}

private SparkUtil() {}

public static boolean isAjax(Request request) {
return "XMLHttpRequest".equals(request.headers("X-Requested-With"));
}

public static String render(String path) {
return render(new HashMap<>(), path);
}

public static String render(Map<String, Object> model, String path) {
if (model == null) {
model = new HashMap<>();
}
return templateEngine.render(new ModelAndView(model, "/templates" + path + ".html"));
}

public static Route view(String path) {
return (request, response) -> render(path);
}

}
98 changes: 98 additions & 0 deletions src/main/java/xyz/sprov/blog/sprovui/util/V2ctlUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package xyz.sprov.blog.sprovui.util;

import org.apache.commons.lang3.StringUtils;
import xyz.sprov.blog.sprovui.bean.InboundTraffic;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class V2ctlUtil {

private static final Pattern octPattern = Pattern.compile("\\\\(\\d{3})");
private static final Pattern inboundPattern = Pattern.compile("stat:\\s*<\\s*name:\\s*\"inbound>>>(?<tag>[^>]+)>>>traffic>>>(?<type>up|down)link\"(\\s*value:\\s*(?<value>\\d+))?");

private static String[] getCommands(int port, String service, String method, String pattern, boolean reset) {
return new String[] {
"sh", "-c",
String.format("/usr/bin/v2ray/v2ctl api --server=127.0.0.1:%d %s.%s 'pattern: \"%s\" reset: %b'", port, service, method, pattern, reset)
};
}

public static Map<String, InboundTraffic> getInboundTraffics(boolean reset) throws IOException, InterruptedException {
int port = Config.getApiPort();
if (port <= 0) {
throw new RuntimeException("错误:未开启 v2ray api");
}
String[] commands = getCommands(port, "StatsService", "QueryStats", "", reset);
String result = ExecUtil.execForResult(10, TimeUnit.SECONDS, commands);
Matcher matcher = inboundPattern.matcher(result);
Map<String, InboundTraffic> inboundTrafficMap = new HashMap<>();
while (matcher.find()) {
String tag = matcher.group("tag");
tag = toUTF_8(tag);
String type = matcher.group("type");
String valueStr = matcher.group("value");
long value;
if (StringUtils.isEmpty(valueStr)) {
value = 0;
} else {
value = Long.parseLong(valueStr);
}
InboundTraffic inboundTraffic = inboundTrafficMap.get(tag);
if (inboundTraffic == null) {
inboundTraffic = new InboundTraffic();
inboundTraffic.setTag(tag);
inboundTrafficMap.put(tag, inboundTraffic);
}
if ("up".equals(type)) {
inboundTraffic.setUplink(value);
} else {
inboundTraffic.setDownlink(value);
}
}
return inboundTrafficMap;
}

private static String toUTF_8(String octStr) throws IOException {
Matcher matcher = octPattern.matcher(octStr);
if (matcher.find()) {
char[] chArr = octStr.toCharArray();
int i = 0;
int length = octStr.length();
StringBuilder builder = new StringBuilder();
do {
int start = matcher.start();
if (i < length) {
if (i != start) {
builder.append(chArr, i, start - i);
}
}
int a = Integer.parseInt(matcher.group(1), 8);
int b = -1, c = -1;
if (matcher.find()) {
b = Integer.parseInt(matcher.group(1), 8);
}
if (matcher.find()) {
c = Integer.parseInt(matcher.group(1), 8);
}
if (b == -1 || c == -1) {
throw new IOException("tag 格式错误:" + octStr);
}
builder.append(new String(new byte[]{(byte) a, (byte) b, (byte) c}));
i = matcher.end();
} while (matcher.find());

if (i < length) {
builder.append(chArr, i, length - i);
}
return builder.toString();
} else {
return octStr;
}
}

}
4 changes: 3 additions & 1 deletion src/main/java/xyz/sprov/blog/sprovui/venum/Protocol.java
Original file line number Diff line number Diff line change
@@ -7,7 +7,9 @@ public enum Protocol {
DOKODEMO_DOOR("dokodemo-door"), // 端口转发
VMESS("vmess"), // VMess协议
MT_PROTO("mtproto"), // Telegram代理
SHADOWSOCKS("shadowsocks"); // shadowsocks
SHADOWSOCKS("shadowsocks"), // shadowsocks
SOCKS("socks"), // socks 代理
HTTP("http"); // http 代理

private static String[] protocolNames;

7 changes: 0 additions & 7 deletions src/main/resources/application-dev.yml

This file was deleted.

15 changes: 0 additions & 15 deletions src/main/resources/application.yml

This file was deleted.

1 change: 1 addition & 0 deletions src/main/resources/static/res/base64/base64.min.js
Loading