perf: set page optimization、API request method(adaptation linux)、Linux certificate installation optimization

This commit is contained in:
putyy
2025-05-10 16:56:54 +08:00
parent 7793f83ea3
commit a37bde428d
17 changed files with 239 additions and 140 deletions

View File

@@ -23,7 +23,7 @@ type App struct {
LockFile string `json:"-"`
PublicCrt []byte `json:"-"`
PrivateKey []byte `json:"-"`
IsProxy bool `json:"-"`
IsProxy bool `json:"IsProxy"`
}
var (

16
core/bind.go Normal file
View File

@@ -0,0 +1,16 @@
package core
type Bind struct {
}
func NewBind() *Bind {
return &Bind{}
}
func (b *Bind) Config() *ResponseData {
return httpServerOnce.buildResp(1, "ok", globalConfig)
}
func (b *Bind) AppInfo() *ResponseData {
return httpServerOnce.buildResp(1, "ok", appOnce)
}

View File

@@ -42,13 +42,8 @@ func (h *HttpServer) run() {
}
fmt.Println("Service started, listening http://" + globalConfig.Host + ":" + globalConfig.Port)
if err1 := http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Host == "127.0.0.1:"+globalConfig.Port && strings.Contains(r.URL.Path, "/cert") {
w.Header().Set("Content-Type", "application/x-x509-ca-data")
w.Header().Set("Content-Disposition", "attachment;filename=res-downloader-public.crt")
w.Header().Set("Content-Transfer-Encoding", "binary")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(appOnce.PublicCrt)))
w.WriteHeader(http.StatusOK)
_, err = io.Copy(w, io.NopCloser(bytes.NewReader(appOnce.PublicCrt)))
if r.Host == "127.0.0.1:"+globalConfig.Port && HandleApi(w, r) {
} else {
proxyOnce.Proxy.ServeHTTP(w, r) // 代理
}
@@ -58,6 +53,15 @@ func (h *HttpServer) run() {
}
}
func (h *HttpServer) downCert(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/x-x509-ca-data")
w.Header().Set("Content-Disposition", "attachment;filename=res-downloader-public.crt")
w.Header().Set("Content-Transfer-Encoding", "binary")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(appOnce.PublicCrt)))
w.WriteHeader(http.StatusOK)
io.Copy(w, io.NopCloser(bytes.NewReader(appOnce.PublicCrt)))
}
func (h *HttpServer) preview(w http.ResponseWriter, r *http.Request) {
realURL := r.URL.Query().Get("url")
if realURL == "" {
@@ -115,7 +119,7 @@ func (h *HttpServer) send(t string, data interface{}) {
runtime.EventsEmit(appOnce.ctx, "event", string(jsonData))
}
func (h *HttpServer) writeJson(w http.ResponseWriter, data ResponseData) {
func (h *HttpServer) writeJson(w http.ResponseWriter, data *ResponseData) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
err := json.NewEncoder(w).Encode(data)
@@ -134,12 +138,7 @@ func (h *HttpServer) error(w http.ResponseWriter, args ...interface{}) {
if len(args) > 1 {
data = args[1]
}
h.writeJson(w, ResponseData{
Code: 0,
Message: message,
Data: data,
})
h.writeJson(w, h.buildResp(0, message, data))
}
func (h *HttpServer) success(w http.ResponseWriter, args ...interface{}) {
@@ -153,12 +152,15 @@ func (h *HttpServer) success(w http.ResponseWriter, args ...interface{}) {
if len(args) > 1 {
message = args[1].(string)
}
h.writeJson(w, h.buildResp(1, message, data))
}
h.writeJson(w, ResponseData{
Code: 1,
func (h *HttpServer) buildResp(code int, message string, data interface{}) *ResponseData {
return &ResponseData{
Code: code,
Message: message,
Data: data,
})
}
}
func (h *HttpServer) openDirectoryDialog(w http.ResponseWriter, r *http.Request) {

View File

@@ -60,6 +60,8 @@ func HandleApi(w http.ResponseWriter, r *http.Request) bool {
httpServerOnce.wxFileDecode(w, r)
case "/api/batch-import":
httpServerOnce.batchImport(w, r)
case "/api/cert":
httpServerOnce.downCert(w, r)
}
return true
}

View File

@@ -64,24 +64,23 @@ func (s *SystemSetup) setProxy() error {
return err
}
is := false
errs := ""
isSuccess := false
var errs strings.Builder
for _, serviceName := range services {
cmds := [][]string{
commands := [][]string{
{"networksetup", "-setwebproxy", serviceName, "127.0.0.1", globalConfig.Port},
{"networksetup", "-setsecurewebproxy", serviceName, "127.0.0.1", globalConfig.Port},
}
for _, args := range cmds {
if output, err := s.runCommand(args); err != nil {
errs = errs + "output:" + string(output) + " err:" + err.Error() + "\n"
fmt.Println("setProxy:", output, " err:", err.Error())
for _, cmd := range commands {
if output, err := s.runCommand(cmd); err != nil {
errs.WriteString(fmt.Sprintf("cmd: %v\noutput: %s\nerr: %s\n", cmd, output, err))
} else {
is = true
isSuccess = true
}
}
}
if is {
if isSuccess {
return nil
}
@@ -94,24 +93,23 @@ func (s *SystemSetup) unsetProxy() error {
return err
}
is := false
errs := ""
isSuccess := false
var errs strings.Builder
for _, serviceName := range services {
cmds := [][]string{
commands := [][]string{
{"networksetup", "-setwebproxystate", serviceName, "off"},
{"networksetup", "-setsecurewebproxystate", serviceName, "off"},
}
for _, args := range cmds {
if output, err := s.runCommand(args); err != nil {
errs = errs + "output:" + string(output) + " err:" + err.Error() + "\n"
fmt.Println("unsetProxy:", output, " err:", err.Error())
for _, cmd := range commands {
if output, err := s.runCommand(cmd); err != nil {
errs.WriteString(fmt.Sprintf("cmd: %v\noutput: %s\nerr: %s\n", cmd, output, err))
} else {
is = true
isSuccess = true
}
}
}
if is {
if isSuccess {
return nil
}

View File

@@ -6,8 +6,22 @@ import (
"bytes"
"fmt"
"os/exec"
"strings"
)
func (s *SystemSetup) getLinuxDistro() (string, error) {
data, err := os.ReadFile("/etc/os-release")
if err != nil {
return "", err
}
for _, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, "ID=") {
return strings.Trim(strings.TrimPrefix(line, "ID="), "\""), nil
}
}
return "", fmt.Errorf("could not determine linux distribution")
}
func (s *SystemSetup) runCommand(args []string) ([]byte, error) {
if len(args) == 0 {
return nil, fmt.Errorf("no command provided")
@@ -33,27 +47,32 @@ func (s *SystemSetup) setProxy() error {
{"gsettings", "set", "org.gnome.system.proxy.https", "host", "127.0.0.1"},
{"gsettings", "set", "org.gnome.system.proxy.https", "port", globalConfig.Port},
}
is := false
errs := ""
isSuccess := false
var errs strings.Builder
for _, cmd := range commands {
if output, err := s.runCommand(cmd); err != nil {
errs = errs + "output:" + string(output) + " err:" + err.Error() + "\n"
fmt.Println(err)
errs.WriteString(fmt.Sprintf("cmd: %v\noutput: %s\nerr: %s\n", cmd, output, err))
} else {
is = true
isSuccess = true
}
}
if is {
if isSuccess {
return nil
}
return fmt.Errorf("failed to set proxy for any active network service, errs:%s", errs)
return fmt.Errorf("failed to set proxy:\n%s", errs.String())
}
func (s *SystemSetup) unsetProxy() error {
cmd := []string{"gsettings", "set", "org.gnome.system.proxy", "mode", "none"}
output, err := s.runCommand(cmd)
return fmt.Errorf("failed to unset proxy for any active network service, errs output:" + string(output) + " err:" + err.Error())
if err != nil {
return fmt.Errorf("failed to unset proxy: %s\noutput: %s", err.Error(), string(output))
}
return nil
}
func (s *SystemSetup) installCert() (string, error) {
@@ -62,35 +81,56 @@ func (s *SystemSetup) installCert() (string, error) {
return "", err
}
actions := [][]string{
{"cp", "-f", s.CertFile, "/usr/local/share/ca-certificates/" + appOnce.AppName + ".crt"},
{"update-ca-certificates"},
{"cp", "-f", s.CertFile, "/usr/share/ca-certificates/trust-source/anchors/" + appOnce.AppName + ".crt"},
{"update-ca-trust"},
{"trust", "extract-compat"},
{"cp", "-f", s.CertFile, "/etc/pki/ca-trust/source/anchors/" + appOnce.AppName + ".crt"},
{"update-ca-trust"},
{"cp", "-f", s.CertFile, "/etc/ssl/ca-certificates/" + appOnce.AppName + ".crt"},
{"update-ca-certificates"},
distro, err := getLinuxDistro()
if err != nil {
return "", fmt.Errorf("detect distro failed: %w", err)
}
is := false
outs := ""
errs := ""
for _, action := range actions {
if output, err1 := s.runCommand(action); err1 != nil {
outs += string(output) + "\n"
errs += err1.Error() + "\n"
fmt.Printf("Failed to execute %v: %v\n", action, err1)
continue
certName := appOnce.AppName + ".crt"
var certPath string
if distro == "deepin" {
certDir := "/usr/share/ca-certificates/" + appOnce.AppName
certPath = certDir + "/" + certName
s.runCommand([]string{"mkdir", "-p", certDir})
} else {
certPath = "/usr/local/share/ca-certificates/" + certName
}
var outs, errs strings.Builder
isSuccess := false
if output, err := s.runCommand([]string{"cp", "-f", s.CertFile, certPath}); err != nil {
errs.WriteString(fmt.Sprintf("copy cert failed: %s\n%s\n", err.Error(), output))
} else {
isSuccess = true
outs.Write(output)
}
if distro == "deepin" {
confPath := "/etc/ca-certificates.conf"
checkCmd := []string{"grep", "-qxF", certName, confPath}
if _, err := s.runCommand(checkCmd); err != nil {
echoCmd := []string{"bash", "-c", fmt.Sprintf("echo '%s' >> %s", certName, confPath)}
if output, err := s.runCommand(echoCmd); err != nil {
errs.WriteString(fmt.Sprintf("append conf failed: %s\n%s\n", err.Error(), output))
} else {
isSuccess = true
outs.Write(output)
}
}
is = true
}
if is {
if output, err := s.runCommand([]string{"update-ca-certificates"}); err != nil {
errs.WriteString(fmt.Sprintf("update failed: %s\n%s\n", err.Error(), output))
} else {
isSuccess = true
outs.Write(output)
}
if isSuccess {
return "", nil
}
return outs, fmt.Errorf("Certificate installation failed, errs:%s", errs)
return outs.String(), fmt.Errorf("certificate installation failed:\n%s", errs.String())
}

View File

@@ -31,8 +31,6 @@ declare module 'vue' {
NModal: typeof import('naive-ui')['NModal']
NModalProvider: typeof import('naive-ui')['NModalProvider']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace']

View File

@@ -1,37 +1,39 @@
import type {AxiosResponse, InternalAxiosRequestConfig} from 'axios'
import axios from 'axios'
import {useIndexStore} from "@/stores";
import {computed} from "vue";
interface RequestOptions {
url: string;
method: 'get' | 'post' | 'put' | 'delete'; // 根据需要扩展
params?: Record<string, any>;
data?: Record<string, any>;
url: string
method: 'get' | 'post' | 'put' | 'delete'
params?: Record<string, any>
data?: Record<string, any>
}
const instance = axios.create({
baseURL: "/",
});
})
instance.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
return config;
return config
},
(error) => {
return Promise.reject(error);
return Promise.reject(error)
}
);
)
instance.interceptors.response.use(
(response: AxiosResponse) => {
return response.data;
return response.data
},
(error) => {
return Promise.reject(error);
}
);
)
const request = ({url, method, params, data}: RequestOptions): Promise<any> => {
return instance({url, method, params, data});
};
return instance({url, method, params, data, baseURL: window.$baseUrl})
}
export default request;
export default request

View File

@@ -47,7 +47,7 @@
<div>{{ store.appInfo.Copyright }}</div>
<div class="flex">
<button class="pl-4" @click="toWebsite('https://s.gowas.cn/d/4089')">论坛</button>
<button class="pl-4" @click="toWebsite('http://127.0.0.1:8899/cert')">证书</button>
<button class="pl-4" @click="toWebsite(certUrl)">证书</button>
<button class="pl-4" @click="toWebsite('https://github.com/putyy/res-downloader')">软件源码</button>
<button class="pl-4" @click="toWebsite('https://github.com/putyy/res-downloader/issues')">帮助支持</button>
<button class="pl-4" @click="toWebsite('https://github.com/putyy/res-downloader/releases')">更新日志</button>
@@ -60,11 +60,14 @@
<script lang="ts" setup>
import {useIndexStore} from "@/stores"
import {BrowserOpenURL} from "../../wailsjs/runtime"
import {computed} from "vue";
const store = useIndexStore()
const props = defineProps(["showModal"])
const emits = defineEmits(["update:showModal"])
const certUrl = computed(()=>{
return store.baseUrl + "/api/cert"
})
const changeShow = (value: boolean) => {
emits('update:showModal', value)
}

View File

@@ -7,7 +7,6 @@
title="管理员授权"
content=""
:show-icon="false"
:closable="false"
:mask-closable="false"
:close-on-esc="false"
class="rounded-lg"

View File

@@ -3,6 +3,8 @@ import {ref} from "vue"
import type {appType} from "@/types/app"
import appApi from "@/api/app"
import {Environment} from "../../wailsjs/runtime"
import * as bind from "../../wailsjs/go/core/Bind"
import {core} from "../../wailsjs/go/models"
export const useIndexStore = defineStore("index-store", () => {
const appInfo = ref<appType.App>({
@@ -40,26 +42,28 @@ export const useIndexStore = defineStore("index-store", () => {
const tableHeight = ref(800)
const isProxy = ref(false)
const baseUrl = ref("")
const init = async () => {
Environment().then((res) => {
envInfo.value = res
})
await getAppInfo()
await appApi.getConfig().then((res) => {
await bind.AppInfo().then((res: core.ResponseData)=>{
appInfo.value = Object.assign({}, appInfo.value, res.data)
isProxy.value = res.data.IsProxy
})
await bind.Config().then((res: core.ResponseData)=>{
globalConfig.value = Object.assign({}, globalConfig.value, res.data)
})
baseUrl.value = "http://"+globalConfig.value.Host + ":" +globalConfig.value.Port
window.$baseUrl = baseUrl.value
window.addEventListener("resize", handleResize);
handleResize()
}
const getAppInfo = async () => {
await appApi.appInfo().then((res) => {
appInfo.value = Object.assign({}, appInfo.value, res.data)
})
}
const setConfig = (formValue: Object) => {
globalConfig.value = Object.assign({}, globalConfig.value, formValue)
appApi.setConfig(globalConfig.value)
@@ -91,8 +95,8 @@ export const useIndexStore = defineStore("index-store", () => {
tableHeight,
isProxy,
envInfo,
baseUrl,
init,
getAppInfo,
setConfig,
openProxy,
unsetProxy

View File

@@ -1,9 +1,10 @@
interface Window {
$loadingBar?: import('naive-ui').LoadingBarProviderInst;
$dialog?: import('naive-ui').DialogProviderInst;
$message?: import('naive-ui').MessageProviderInst;
$notification?: import('naive-ui').NotificationProviderInst;
$ws?: WebSocket;
$loadingBar?: import('naive-ui').LoadingBarProviderInst
$dialog?: import('naive-ui').DialogProviderInst
$message?: import('naive-ui').MessageProviderInst
$notification?: import('naive-ui').NotificationProviderInst
$ws?: WebSocket
$baseUrl?: string
}
declare module '*.vue' {

View File

@@ -46,6 +46,38 @@
</NTooltip>
</NFormItem>
<NFormItem label="保存位置" path="SaveDirectory">
<NInput :value="formValue.SaveDirectory" placeholder="保存位置"/>
<NButton strong secondary type="primary" @click="selectDir" class="ml-1">选择</NButton>
</NFormItem>
<div class="grid grid-cols-2">
<NFormItem label="文件命名" path="FilenameLen">
<NInputNumber v-model:value="formValue.FilenameLen" :min="0" :max="9999" placeholder="0"/>
<NSwitch v-model:value="formValue.FilenameTime" class="ml-1"></NSwitch>
<NTooltip trigger="hover">
<template #trigger>
<NIcon size="18" class="ml-1 text-gray-500">
<HelpCircleOutline/>
</NIcon>
</template>
输入框控制文件命名的长度(不含时间0为无效)开关控制文件末尾是否添加时间标识
</NTooltip>
</NFormItem>
<NFormItem label="清晰度" path="Quality">
<NSelect v-model:value="formValue.Quality" :options="options"/>
<NTooltip trigger="hover">
<template #trigger>
<NIcon size="18" class="ml-1 text-gray-500">
<HelpCircleOutline/>
</NIcon>
</template>
视频号有效
</NTooltip>
</NFormItem>
</div>
<div class="grid grid-cols-2 gap-4">
<NFormItem label="自动拦截" path="AutoProxy">
<NSwitch v-model:value="formValue.AutoProxy"/>
@@ -99,46 +131,6 @@
</div>
<div class="grid grid-cols-2 gap-4">
<NFormItem label="保存位置" path="SaveDirectory">
<NInput :value="formValue.SaveDirectory" placeholder="保存位置"/>
<NButton strong secondary type="primary" @click="selectDir" class="ml-1">选择</NButton>
</NFormItem>
<NFormItem label="文件命名" path="FilenameLen">
<NInputNumber v-model:value="formValue.FilenameLen" :min="0" :max="9999" placeholder="0"/>
<NSwitch v-model:value="formValue.FilenameTime" class="ml-1"></NSwitch>
<NTooltip trigger="hover">
<template #trigger>
<NIcon size="18" class="ml-1 text-gray-500">
<HelpCircleOutline/>
</NIcon>
</template>
输入框控制文件命名的长度(不含时间0为无效)开关控制文件末尾是否添加时间标识
</NTooltip>
</NFormItem>
</div>
<div class="grid grid-cols-2 gap-4">
<NFormItem label="主题" path="theme">
<NRadioGroup v-model:value="formValue.Theme" name="theme">
<NRadio value="lightTheme">浅色</NRadio>
<NRadio value="darkTheme">深色</NRadio>
</NRadioGroup>
</NFormItem>
<NFormItem label="清晰度" path="Quality">
<NSelect v-model:value="formValue.Quality" :options="options"/>
<NTooltip trigger="hover">
<template #trigger>
<NIcon size="18" class="ml-1 text-gray-500">
<HelpCircleOutline/>
</NIcon>
</template>
视频号有效
</NTooltip>
</NFormItem>
</div>
<NFormItem label="UserAgent" path="UserAgent">
<NInput v-model:value="formValue.UserAgent" placeholder="默认UserAgent"/>
<NTooltip trigger="hover">

7
frontend/wailsjs/go/core/Bind.d.ts vendored Executable file
View File

@@ -0,0 +1,7 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {core} from '../models';
export function AppInfo():Promise<core.ResponseData>;
export function Config():Promise<core.ResponseData>;

View File

@@ -0,0 +1,11 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function AppInfo() {
return window['go']['core']['Bind']['AppInfo']();
}
export function Config() {
return window['go']['core']['Bind']['Config']();
}

21
frontend/wailsjs/go/models.ts Executable file
View File

@@ -0,0 +1,21 @@
export namespace core {
export class ResponseData {
code: number;
message: string;
data: any;
static createFrom(source: any = {}) {
return new ResponseData(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.code = source["code"];
this.message = source["message"];
this.data = source["data"];
}
}
}

View File

@@ -29,6 +29,7 @@ var wailsJson string
func main() {
// Create an instance of the app structure
app := core.GetApp(assets, wailsJson)
bind := core.NewBind()
isMac := runtime.GOOS == "darwin"
// menu
appMenu := menu.NewMenu()
@@ -68,7 +69,9 @@ func main() {
OnShutdown: func(ctx context.Context) {
app.OnExit()
},
Bind: []interface{}{},
Bind: []interface{}{
bind,
},
Mac: &mac.Options{
TitleBar: mac.TitleBarHiddenInset(),
About: &mac.AboutInfo{