内嵌 http 服务

This commit is contained in:
shikong 2024-02-17 21:45:43 +08:00
parent d3798179cf
commit b1fab44027
52 changed files with 2729 additions and 38 deletions

1
.gitignore vendored
View File

@ -61,3 +61,4 @@ dist/
npm-debug.log
yarn-error.log
./config.toml

15
.idea/git_toolbox_prj.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

6
.idea/jpa-buddy.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JpaBuddyIdeaProjectConfig">
<option name="renamerInitialized" value="true" />
</component>
</project>

9
.idea/misc.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="ProjectType">
<option name="id" value="jpab" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/wails-app-dock.iml" filepath="$PROJECT_DIR$/.idea/wails-app-dock.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

10
.idea/wails-app-dock.iml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -13,3 +13,5 @@ wails init -n [项目名称] -t wails-vue3-template
```shell
wails init -n [项目名称] -t https://github.com/misitebao/wails-template-vue
```
### 生成 wails binding

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import {WindowMinimise} from "../wailsjs/runtime";
import {Exit} from "../wailsjs/go/main/App";
import {ForceClose} from "../wailsjs/go/core/App";
const { t, availableLocales: languages, locale } = useI18n();
@ -14,7 +14,7 @@ const onclickMinimise = () => {
};
const onclickQuit = () => {
Exit()
ForceClose()
};
document.body.addEventListener("click", function (event) {

9
frontend/wailsjs/go/core/App.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {context} from '../models';
export function Ctx():Promise<context.Context>;
export function ForceClose():Promise<void>;
export function ReloadApp():Promise<void>;

View File

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

View File

@ -1,4 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function Exit():Promise<void>;

View File

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

49
go.mod
View File

@ -1,37 +1,76 @@
module changeme
module skapp
go 1.18
require github.com/wailsapp/wails/v2 v2.8.0
require (
github.com/gin-contrib/gzip v0.0.6
github.com/gin-gonic/gin v1.9.1
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.18.0
github.com/goccy/go-json v0.10.2
github.com/pelletier/go-toml/v2 v2.1.1
github.com/sony/sonyflake v1.2.0
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3
github.com/wailsapp/wails/v2 v2.8.0
go.uber.org/atomic v1.6.0
go.uber.org/zap v1.13.0
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/jsonreference v0.20.4 // indirect
github.com/go-openapi/spec v0.20.14 // indirect
github.com/go-openapi/swag v0.22.9 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/labstack/echo/v4 v4.10.2 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.0 // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/leaanthony/u v1.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/tkrajina/go-reflector v0.5.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/go-webview2 v1.0.10 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/crypto v0.18.0 // indirect
go.uber.org/multierr v1.5.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.18.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
// replace github.com/wailsapp/wails/v2 v2.8.0 => C:\Users\Administrator\go\pkg\mod

187
go.sum
View File

@ -1,16 +1,79 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4=
github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do=
github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw=
github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE=
github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
@ -26,6 +89,11 @@ github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI=
github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
@ -35,8 +103,18 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -44,13 +122,40 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
@ -62,31 +167,101 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.8.0 h1:b2NNn99uGPiN6P5bDsnPwOJZWtAOUhNLv7Vl+YxMTr4=
github.com/wailsapp/wails/v2 v2.8.0/go.mod h1:EFUGWkUX3KofO4fmKR/GmsLy3HhPH7NbyOEaMt8lBF0=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

27
main.go
View File

@ -3,6 +3,7 @@ package main
import (
"embed"
"log"
"skapp/pkg/core"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
@ -22,18 +23,19 @@ var icon []byte
func main() {
// Create an instance of the app structure
// 创建一个App结构体实例
app := NewApp()
app := core.NewApp()
// Create application with options
// 使用选项创建应用
err := wails.Run(&options.App{
Title: "wails-app-dock",
Width: 900,
Height: 600,
MinWidth: 900,
MinHeight: 600,
MaxWidth: 1200,
MaxHeight: 800,
Width: 1024,
Height: 768,
MinWidth: 1024,
MinHeight: 768,
MaxWidth: 0,
MaxHeight: 0,
DisableResize: false,
Fullscreen: false,
Frameless: false,
@ -43,15 +45,14 @@ func main() {
Menu: nil,
Logger: nil,
LogLevel: logger.DEBUG,
OnStartup: app.startup,
OnDomReady: app.domReady,
OnBeforeClose: app.beforeClose,
OnShutdown: app.shutdown,
OnStartup: app.Startup,
OnDomReady: app.DomReady,
OnBeforeClose: app.BeforeClose,
OnShutdown: app.Shutdown,
WindowStartState: options.Normal,
AssetServer: &assetserver.Options{
Assets: assets,
Handler: nil,
Middleware: nil,
Handler: app.Handler,
},
Bind: []interface{}{
app,

28
pkg/config/config.go Normal file
View File

@ -0,0 +1,28 @@
package config
type Config struct {
Server *ServerConfig `toml:"server" json:"server" comment:"服务配置"`
Debug *DebuggerConfig `toml:"debug" json:"debug" comment:"调试模式"`
}
type ServerConfig struct {
Host string `toml:"host" json:"host" comment:"监听ip"`
Port int `toml:"port" json:"port" comment:"监听端口"`
}
type DebuggerConfig struct {
Enable bool `toml:"enable" json:"enable" comment:"启用"`
}
func (c *Config) HasDebug() bool {
return c.Debug != nil
}
func DefaultConfig() *Config {
return &Config{
Server: &ServerConfig{
Host: "localhost",
Port: 12121,
},
}
}

50
pkg/config/toml/config.go Normal file
View File

@ -0,0 +1,50 @@
package toml
import (
"github.com/pelletier/go-toml/v2"
"os"
"path/filepath"
"skapp/pkg/logger"
"skapp/pkg/config"
)
func LoadConfig() (*config.Config, error) {
p, _ := filepath.Abs("./config.toml")
logger.Log.Infof("配置文件路径 %s", p)
flag := os.O_RDWR
_, err := os.Stat(p)
exist := !os.IsNotExist(err)
if !exist {
f, err := os.OpenFile(p, flag|os.O_CREATE, 0644)
if err != nil {
return nil, err
}
defer func() {
_ = f.Close()
}()
encoder := toml.NewEncoder(f)
encoder.SetIndentTables(true)
_ = encoder.Encode(config.DefaultConfig())
_ = f.Sync()
}
f, err := os.OpenFile(p, flag, 0644)
if err != nil {
return nil, err
}
defer func() {
_ = f.Close()
}()
c := &config.Config{}
decoder := toml.NewDecoder(f)
err = decoder.Decode(c)
if err != nil {
return nil, err
}
return c, nil
}

View File

@ -0,0 +1,10 @@
package toml
import "testing"
func TestLoadConfig(t *testing.T) {
_, err := LoadConfig()
if err != nil {
t.Fatal(err)
}
}

132
pkg/core/app.go Normal file
View File

@ -0,0 +1,132 @@
package core
import (
"context"
"fmt"
"github.com/wailsapp/wails/v2/pkg/runtime"
"go.uber.org/zap/zapcore"
"net"
"net/http"
"os"
"skapp/pkg/config/toml"
"skapp/pkg/global"
"skapp/pkg/logger"
"skapp/pkg/pid"
server "skapp/pkg/server"
"time"
)
var pidFile = "./.pid"
// App struct
type App struct {
ctx context.Context
srv *http.Server
Handler http.Handler
pidLock *pid.PidLock
}
// NewApp creates a new App application struct
func NewApp() *App {
app := &App{}
app.load()
return app
}
func (a *App) Ctx() context.Context {
return a.ctx
}
func (a *App) ReloadApp() {
runtime.WindowReloadApp(a.ctx)
}
func (a *App) ForceClose() {
a.Shutdown(a.ctx)
os.Exit(0)
}
func (a *App) load() {
a.pidLock = pid.NewPidLock(pidFile)
conf, err := toml.LoadConfig()
if err != nil {
logger.Log.Fatalf("%s", err)
}
global.Config = conf
if conf.HasDebug() && conf.Debug.Enable {
logger.SetLevel(zapcore.DebugLevel)
} else {
logger.SetLevel(zapcore.ErrorLevel)
}
engine := server.Server(conf)
a.srv = &http.Server{
Addr: fmt.Sprintf("%s:%d", conf.Server.Host, conf.Server.Port),
Handler: engine,
}
a.Handler = engine.Handler()
}
func (a *App) Startup(ctx context.Context) {
if !global.Config.HasDebug() || !global.Config.Debug.Enable {
err := a.pidLock.Lock()
if err != nil {
os.Exit(-1)
}
}
a.ctx = ctx
go func() {
logger.Log.Infof("启动本地后台服务: %s", a.srv.Addr)
// 服务连接
if err := a.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
switch err.(type) {
case *net.OpError:
_, _ = runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
Title: "错误",
Type: runtime.ErrorDialog,
Message: fmt.Sprintf("服务启动失败: 请检查 %s 是否被其他进程占用", a.srv.Addr),
})
default:
_, _ = runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
Title: "错误",
Type: runtime.ErrorDialog,
Message: err.Error(),
})
}
logger.Log.Fatalf("listen: %s\n", err)
os.Exit(-1)
}
}()
}
// DomReady is called after the front-end dom has been loaded
// domReady 在前端Dom加载完毕后调用
func (a *App) DomReady(ctx context.Context) {
// Add your action here
// 在这里添加你的操作
}
func (a *App) BeforeClose(ctx context.Context) bool {
return false
}
func (a *App) Shutdown(ctx context.Context) {
ctx2, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if !global.Config.HasDebug() || !global.Config.Debug.Enable {
a.pidLock.UnLock()
}
if err := a.srv.Shutdown(ctx2); err != nil {
logger.Log.Fatalf("Server Shutdown:", err)
}
}

5
pkg/global/global.go Normal file
View File

@ -0,0 +1,5 @@
package global
import "skapp/pkg/config"
var Config *config.Config

63
pkg/logger/adapter.go Normal file
View File

@ -0,0 +1,63 @@
package logger
import "go.uber.org/zap"
type WailsZapLoggerAdaptor struct {
logger *zap.SugaredLogger
}
func (w *WailsZapLoggerAdaptor) Print(message string) {
w.logger.Info(message)
}
func (w *WailsZapLoggerAdaptor) Trace(message string) {
w.logger.Error(message)
}
func (w *WailsZapLoggerAdaptor) Debug(message string) {
w.logger.Debug(message)
}
func (w *WailsZapLoggerAdaptor) Debugf(template string, args ...interface{}) {
w.logger.Debugf(template, args...)
}
func (w *WailsZapLoggerAdaptor) Info(message string) {
w.logger.Info(message)
}
func (w *WailsZapLoggerAdaptor) Infof(template string, args ...interface{}) {
w.logger.Infof(template, args...)
}
func (w *WailsZapLoggerAdaptor) Warning(message string) {
w.logger.Warn(message)
}
func (w *WailsZapLoggerAdaptor) Warn(message string) {
w.Warning(message)
}
func (w *WailsZapLoggerAdaptor) Warnf(message string, args ...interface{}) {
w.logger.Warnf(message, args...)
}
func (w *WailsZapLoggerAdaptor) Error(message string) {
w.logger.Error(message)
}
func (w *WailsZapLoggerAdaptor) Errorln(err error) {
w.logger.Error(err)
}
func (w *WailsZapLoggerAdaptor) Errorf(template string, args ...interface{}) {
w.logger.Errorf(template, args...)
}
func (w *WailsZapLoggerAdaptor) Fatal(message string) {
w.logger.Fatal(message)
}
func (w *WailsZapLoggerAdaptor) Fatalf(message string, args ...interface{}) {
w.logger.Fatalf(message, args...)
}

57
pkg/logger/logger.go Normal file
View File

@ -0,0 +1,57 @@
package logger
import (
"go.uber.org/atomic"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
var Log *WailsZapLoggerAdaptor
var sugar *zap.SugaredLogger
var logger *zap.Logger
var level = &atomic.String{}
func init() {
SetLevel(zapcore.DebugLevel)
encoder := zapcore.NewConsoleEncoder(DefaultEncoderConfig())
multiWriteSyncer := zapcore.NewMultiWriteSyncer(DefaultConsoleSyncer())
core := zapcore.NewCore(encoder, multiWriteSyncer, DefaultLevelEnabler())
logger = zap.New(core, zap.AddCaller())
defer func() {
_ = logger.Sync()
}()
sugar = logger.Sugar()
Log = &WailsZapLoggerAdaptor{logger: sugar}
}
func DefaultTimeEncoder() (timeEncoder zapcore.TimeEncoder) {
timeEncoder = zapcore.RFC3339TimeEncoder
return
}
func SetLevel(l zapcore.Level) {
level.Store(l.String())
}
func DefaultLevelEnabler() zap.LevelEnablerFunc {
return func(z zapcore.Level) bool {
atomicLevel := zap.NewAtomicLevel()
_ = atomicLevel.UnmarshalText([]byte(level.Load()))
return z >= atomicLevel.Level()
}
}
func DefaultEncoderConfig() (encoderConfig zapcore.EncoderConfig) {
encoderConfig = zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = DefaultTimeEncoder()
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return
}
func DefaultConsoleSyncer() zapcore.WriteSyncer {
return zapcore.AddSync(os.Stdout)
}

17
pkg/logger/logger_test.go Normal file
View File

@ -0,0 +1,17 @@
package logger
import (
"go.uber.org/zap/zapcore"
"testing"
"time"
)
func TestLogger(t *testing.T) {
Log.Debugf("%s", time.Now().Format("2006-01-02 15:04:05.000"))
Log.Infof("%s", time.Now().Format("2006-01-02 15:04:05.000"))
SetLevel(zapcore.InfoLevel)
Log.Debugf("%s", time.Now().Format("2006-01-02 15:04:05.000"))
Log.Infof("%s", time.Now().Format("2006-01-02 15:04:05.000"))
}

24
pkg/pid/pid.go Normal file
View File

@ -0,0 +1,24 @@
package pid
import (
"errors"
"os"
"path/filepath"
)
var (
LockFail = errors.New("进程锁创建失败")
)
type PidLock struct {
file string
lock *os.File
}
func NewPidLock(path string) *PidLock {
absPath, _ := filepath.Abs(path)
return &PidLock{
file: absPath,
}
}

21
pkg/pid/pid_test.go Normal file
View File

@ -0,0 +1,21 @@
package pid
import (
"testing"
"time"
)
func TestPidLock_Lock(t *testing.T) {
name := "./.pid"
lock := NewPidLock(name)
err := lock.Lock()
defer func(lock *PidLock) {
lock.UnLock()
}(lock)
if err != nil {
t.Fatal(t)
}
time.Sleep(10 * time.Second)
}

36
pkg/pid/pid_unix.go Normal file
View File

@ -0,0 +1,36 @@
//go:build !windows
package pid
import (
"os"
"skapp/pkg/logger"
"syscall"
)
func (p *PidLock) Lock() error {
_, err := os.Stat(p.file)
if err == nil {
os.Exit(0)
}
pidFileHandle, err := os.OpenFile(pidFile, os.O_RDONLY|os.O_CREATE, os.ModePerm)
if err != nil {
logger.Log.Fatalf("启动失败 进程锁文件 .pid 创建失败")
return LockFail
}
err = syscall.Flock(int(pidFileHandle.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err != nil {
logger.Log.Fatalf("启动失败 进程锁文件 .pid 被占用")
return LockFail
}
p.lock = pidFileHandle
return nil
}
func (p *PidLock) UnLock() {
_ = syscall.Flock(int(p.lock.Fd()), syscall.LOCK_UN)
_ = p.lock.Close()
_ = os.Remove(p.file)
}

119
pkg/pid/pid_windows.go Normal file
View File

@ -0,0 +1,119 @@
//go:build windows
package pid
import (
"errors"
"os"
"skapp/pkg/logger"
"syscall"
"unsafe"
)
var pidFile = "./.pid"
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procLockFileEx = kernel32.NewProc("LockFileEx")
procUnlockFileEx = kernel32.NewProc("UnlockFileEx")
)
const (
fileFlagNormal = 0x00000080
lockfileExclusiveLock = 0x00000002
lockfileFailImmediately = 0x00000001
)
func (p *PidLock) Lock() error {
_, err := os.Stat(p.file)
if err == nil {
os.Exit(0)
}
f, err := os.OpenFile(pidFile, os.O_RDONLY|os.O_CREATE, os.ModePerm)
if err != nil {
logger.Log.Fatalf("启动失败 进程锁文件 .pid 创建失败")
return LockFail
}
p.lock = f
_ = f.Sync()
err = TryLockEX(f)
if err != nil {
logger.Log.Fatal(err.Error())
return LockFail
}
return nil
}
// LockSH places a shared lock on the file. If the file is already locked, waits until the file is released.
func LockSH(fp *os.File) error {
r1, errNo := wlock(fp, 0x0)
return isWError(r1, errNo)
}
// LockEX places an exclusive lock on the file. If the file is already locked, waits until the file is released.
func LockEX(fp *os.File) error {
r1, errNo := wlock(fp, lockfileExclusiveLock)
return isWError(r1, errNo)
}
// TryLockSH places a shared lock on the file. If the file is already locked, returns an error immediately.
func TryLockSH(fp *os.File) error {
r1, errNo := wlock(fp, lockfileFailImmediately)
return isWError(r1, errNo)
}
// TryLockEX places an exclusive lock on the file. If the file is already locked, returns an error immediately.
func TryLockEX(fp *os.File) error {
r1, errNo := wlock(fp, lockfileExclusiveLock|lockfileFailImmediately)
return isWError(r1, errNo)
}
// Unlock the file.
func Unlock(fp *os.File) error {
r1, _, errNo := syscall.SyscallN(
procUnlockFileEx.Addr(),
fp.Fd(),
uintptr(0),
uintptr(1),
uintptr(0),
uintptr(unsafe.Pointer(&syscall.Overlapped{})),
0,
)
return isWError(r1, errNo)
}
func wlock(fp *os.File, flags uintptr) (uintptr, syscall.Errno) {
r1, _, errNo := syscall.SyscallN(
procLockFileEx.Addr(),
fp.Fd(),
flags,
uintptr(0),
uintptr(1),
uintptr(0),
uintptr(unsafe.Pointer(&syscall.Overlapped{})),
)
return r1, errNo
}
func isWError(r1 uintptr, errNo syscall.Errno) error {
if r1 != 1 {
if errNo != 0 {
return errors.New(errNo.Error())
} else {
return syscall.EINVAL
}
}
return nil
}
func (p *PidLock) UnLock() {
_ = Unlock(p.lock)
_ = p.lock.Close()
_ = os.Remove(p.file)
}

View File

@ -0,0 +1,22 @@
package controller
import (
"github.com/gin-gonic/gin"
"skapp/pkg/global"
"skapp/pkg/utils/response"
)
// PostConfig
// List 获取配置
//
// @Summary 获取应用列表
// @Description 获取应用列表
// @Tags Config
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=config.Config}
// @Failure default {object} errorx.CodeErrorResponse
// @Router /config/ [post]
func PostConfig(ctx *gin.Context) {
ctx.JSON(200, response.NewResponse(global.Config))
}

View File

@ -0,0 +1,88 @@
package controller
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"os"
"path/filepath"
"skapp/pkg/logger"
"skapp/pkg/utils/errorx"
"skapp/pkg/utils/response"
)
type GetPathDto struct {
// 路径
Path string `form:"path" binding:"required" example:"C:\\Windows\\System32\\cmd.exe"`
}
type GetFileDto struct {
// 路径
Path string `form:"path" binding:"required" example:"C:\\Windows\\System32\\cmd.exe"`
// 偏移量
Offset int64 `form:"offset"`
// 区块大小
ChunkSize int64 `form:"chunkSize"`
}
// GetPath
// @Summary 获取文件路径
// @Description 获取文件路径
// @Tags File
// @Accept json
// @Produce json
// @Param vo query controller.GetPathDto true "获取文件路径"
// @Success 200 {object} response.Response{data=string}
// @Failure default {object} errorx.CodeErrorResponse
// @Router /file/path [get]
func GetPath(ctx *gin.Context) {
dto := &GetPathDto{}
err := ctx.ShouldBindQuery(dto)
if err = errorx.ParseError(err); err != nil {
logger.Log.Errorln(err)
ctx.JSON(200, err)
}
logger.Log.Infof("%+v", dto)
absPath, _ := filepath.Abs(dto.Path)
ctx.JSON(200, response.NewResponse(absPath))
}
// GetFile
// @Summary 获取文件
// @Description 获取文件
// @Tags File
// @Accept json
// @Produce octet-stream
// @Param vo query controller.GetFileDto true "获取文件"
// @Router /file/ [get]
func GetFile(ctx *gin.Context) {
dto := &GetFileDto{}
err := ctx.BindQuery(dto)
if err != nil {
ctx.Status(http.StatusInternalServerError)
logger.Log.Errorln(err)
return
}
offset := dto.Offset
chunkSize := dto.ChunkSize
p, _ := filepath.Abs(dto.Path)
info, err := os.Stat(p)
if err != nil || info.IsDir() {
ctx.Status(http.StatusNotFound)
return
}
if offset == 0 && chunkSize == 0 {
ctx.File(p)
return
} else {
w := ctx.Writer
ctx.Request.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+chunkSize))
logger.Log.Debugf("%s", ctx.Request.Header.Get("Range"))
http.ServeFile(w, ctx.Request, p)
return
}
}

View File

@ -0,0 +1,43 @@
package controller
import (
"fmt"
"github.com/gin-gonic/gin"
"skapp/pkg/server/services/wol"
"skapp/pkg/utils/errorx"
"skapp/pkg/utils/response"
)
type WakeupDTO struct {
// mac 地址
Mac string `json:"mac" example:"FF-FF-FF-FF-FF-FF-FF"`
// 端口
Port int `json:"port" example:"9"`
}
// PostWolWakeUP
// @Summary wol 唤醒
// @Description wol 唤醒
// @Tags Wol
// @Accept json
// @Produce json
// @Param vo body WakeupDTO true "局域网唤醒"
// @Success 200 {object} response.Response{data=string}
// @Failure default {object} errorx.CodeErrorResponse
// @Router /wol/wakeup [post]
func PostWolWakeUP(ctx *gin.Context) {
dto := &WakeupDTO{
Mac: "FF:FF:FF:FF:FF:FF",
Port: 9,
}
_ = ctx.BindJSON(dto)
err := wol.Services.WakeUp(dto.Mac, dto.Port)
if err = errorx.ParseError(err); err != nil {
ctx.JSON(200, err)
return
}
ctx.JSON(200, response.NewResponse(fmt.Sprintf("%s 唤醒包发送成功", dto.Mac)))
}

409
pkg/server/docs/docs.go Normal file
View File

@ -0,0 +1,409 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "Shikong",
"email": "919411476@qq.com"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/config/": {
"post": {
"description": "获取应用列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Config"
],
"summary": "获取应用列表",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/config.Config"
}
}
}
]
}
},
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
}
},
"/error": {
"get": {
"description": "错误信息示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Error"
],
"summary": "错误处理",
"responses": {
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
},
"put": {
"description": "错误信息示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Error"
],
"summary": "错误处理",
"responses": {
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
},
"post": {
"description": "错误信息示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Error"
],
"summary": "错误处理",
"responses": {
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
},
"delete": {
"description": "错误信息示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Error"
],
"summary": "错误处理",
"responses": {
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
},
"patch": {
"description": "错误信息示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Error"
],
"summary": "错误处理",
"responses": {
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
}
},
"/file/": {
"get": {
"description": "获取文件",
"consumes": [
"application/json"
],
"produces": [
"application/octet-stream"
],
"tags": [
"File"
],
"summary": "获取文件",
"parameters": [
{
"type": "integer",
"description": "区块大小",
"name": "chunkSize",
"in": "query"
},
{
"type": "integer",
"description": "偏移量",
"name": "offset",
"in": "query"
},
{
"type": "string",
"example": "C:\\Windows\\System32\\cmd.exe",
"description": "路径",
"name": "path",
"in": "query",
"required": true
}
],
"responses": {}
}
},
"/file/path": {
"get": {
"description": "获取文件路径",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"File"
],
"summary": "获取文件路径",
"parameters": [
{
"type": "string",
"example": "C:\\Windows\\System32\\cmd.exe",
"description": "路径",
"name": "path",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"type": "string"
}
}
}
]
}
},
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
}
},
"/wol/wakeup": {
"post": {
"description": "wol 唤醒",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Wol"
],
"summary": "wol 唤醒",
"parameters": [
{
"description": "局域网唤醒",
"name": "vo",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/controller.WakeupDTO"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"type": "string"
}
}
}
]
}
},
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
}
}
},
"definitions": {
"config.Config": {
"type": "object",
"properties": {
"debug": {
"$ref": "#/definitions/config.DebuggerConfig"
},
"server": {
"$ref": "#/definitions/config.ServerConfig"
}
}
},
"config.DebuggerConfig": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
}
}
},
"config.ServerConfig": {
"type": "object",
"properties": {
"host": {
"type": "string"
},
"port": {
"type": "integer"
}
}
},
"controller.WakeupDTO": {
"type": "object",
"properties": {
"mac": {
"description": "mac 地址",
"type": "string",
"example": "FF-FF-FF-FF-FF-FF-FF"
},
"port": {
"description": "端口",
"type": "integer",
"example": 9
}
}
},
"errorx.CodeErrorResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
},
"data": {},
"msg": {
"type": "string",
"example": "OK"
}
}
},
"response.Response": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
},
"data": {},
"msg": {
"type": "string",
"example": "OK"
}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "/s",
Schemes: []string{},
Title: "Wails AssetServer API",
Description: "Wails AssetServer API 文档",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

View File

@ -0,0 +1,384 @@
{
"swagger": "2.0",
"info": {
"description": "Wails AssetServer API 文档",
"title": "Wails AssetServer API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "Shikong",
"email": "919411476@qq.com"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"basePath": "/s",
"paths": {
"/config/": {
"post": {
"description": "获取应用列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Config"
],
"summary": "获取应用列表",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/config.Config"
}
}
}
]
}
},
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
}
},
"/error": {
"get": {
"description": "错误信息示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Error"
],
"summary": "错误处理",
"responses": {
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
},
"put": {
"description": "错误信息示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Error"
],
"summary": "错误处理",
"responses": {
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
},
"post": {
"description": "错误信息示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Error"
],
"summary": "错误处理",
"responses": {
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
},
"delete": {
"description": "错误信息示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Error"
],
"summary": "错误处理",
"responses": {
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
},
"patch": {
"description": "错误信息示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Error"
],
"summary": "错误处理",
"responses": {
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
}
},
"/file/": {
"get": {
"description": "获取文件",
"consumes": [
"application/json"
],
"produces": [
"application/octet-stream"
],
"tags": [
"File"
],
"summary": "获取文件",
"parameters": [
{
"type": "integer",
"description": "区块大小",
"name": "chunkSize",
"in": "query"
},
{
"type": "integer",
"description": "偏移量",
"name": "offset",
"in": "query"
},
{
"type": "string",
"example": "C:\\Windows\\System32\\cmd.exe",
"description": "路径",
"name": "path",
"in": "query",
"required": true
}
],
"responses": {}
}
},
"/file/path": {
"get": {
"description": "获取文件路径",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"File"
],
"summary": "获取文件路径",
"parameters": [
{
"type": "string",
"example": "C:\\Windows\\System32\\cmd.exe",
"description": "路径",
"name": "path",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"type": "string"
}
}
}
]
}
},
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
}
},
"/wol/wakeup": {
"post": {
"description": "wol 唤醒",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Wol"
],
"summary": "wol 唤醒",
"parameters": [
{
"description": "局域网唤醒",
"name": "vo",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/controller.WakeupDTO"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.Response"
},
{
"type": "object",
"properties": {
"data": {
"type": "string"
}
}
}
]
}
},
"default": {
"description": "",
"schema": {
"$ref": "#/definitions/errorx.CodeErrorResponse"
}
}
}
}
}
},
"definitions": {
"config.Config": {
"type": "object",
"properties": {
"debug": {
"$ref": "#/definitions/config.DebuggerConfig"
},
"server": {
"$ref": "#/definitions/config.ServerConfig"
}
}
},
"config.DebuggerConfig": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
}
}
},
"config.ServerConfig": {
"type": "object",
"properties": {
"host": {
"type": "string"
},
"port": {
"type": "integer"
}
}
},
"controller.WakeupDTO": {
"type": "object",
"properties": {
"mac": {
"description": "mac 地址",
"type": "string",
"example": "FF-FF-FF-FF-FF-FF-FF"
},
"port": {
"description": "端口",
"type": "integer",
"example": 9
}
}
},
"errorx.CodeErrorResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
},
"data": {},
"msg": {
"type": "string",
"example": "OK"
}
}
},
"response.Response": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
},
"data": {},
"msg": {
"type": "string",
"example": "OK"
}
}
}
}
}

View File

@ -0,0 +1,248 @@
basePath: /s
definitions:
config.Config:
properties:
debug:
$ref: '#/definitions/config.DebuggerConfig'
server:
$ref: '#/definitions/config.ServerConfig'
type: object
config.DebuggerConfig:
properties:
enable:
type: boolean
type: object
config.ServerConfig:
properties:
host:
type: string
port:
type: integer
type: object
controller.WakeupDTO:
properties:
mac:
description: mac 地址
example: FF-FF-FF-FF-FF-FF-FF
type: string
port:
description: 端口
example: 9
type: integer
type: object
errorx.CodeErrorResponse:
properties:
code:
example: 200
type: integer
data: {}
msg:
example: OK
type: string
type: object
response.Response:
properties:
code:
example: 200
type: integer
data: {}
msg:
example: OK
type: string
type: object
info:
contact:
email: 919411476@qq.com
name: Shikong
description: Wails AssetServer API 文档
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: Wails AssetServer API
version: "1.0"
paths:
/config/:
post:
consumes:
- application/json
description: 获取应用列表
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
$ref: '#/definitions/config.Config'
type: object
default:
description: ""
schema:
$ref: '#/definitions/errorx.CodeErrorResponse'
summary: 获取应用列表
tags:
- Config
/error:
delete:
consumes:
- application/json
description: 错误信息示例
produces:
- application/json
responses:
default:
description: ""
schema:
$ref: '#/definitions/errorx.CodeErrorResponse'
summary: 错误处理
tags:
- Error
get:
consumes:
- application/json
description: 错误信息示例
produces:
- application/json
responses:
default:
description: ""
schema:
$ref: '#/definitions/errorx.CodeErrorResponse'
summary: 错误处理
tags:
- Error
patch:
consumes:
- application/json
description: 错误信息示例
produces:
- application/json
responses:
default:
description: ""
schema:
$ref: '#/definitions/errorx.CodeErrorResponse'
summary: 错误处理
tags:
- Error
post:
consumes:
- application/json
description: 错误信息示例
produces:
- application/json
responses:
default:
description: ""
schema:
$ref: '#/definitions/errorx.CodeErrorResponse'
summary: 错误处理
tags:
- Error
put:
consumes:
- application/json
description: 错误信息示例
produces:
- application/json
responses:
default:
description: ""
schema:
$ref: '#/definitions/errorx.CodeErrorResponse'
summary: 错误处理
tags:
- Error
/file/:
get:
consumes:
- application/json
description: 获取文件
parameters:
- description: 区块大小
in: query
name: chunkSize
type: integer
- description: 偏移量
in: query
name: offset
type: integer
- description: 路径
example: C:\Windows\System32\cmd.exe
in: query
name: path
required: true
type: string
produces:
- application/octet-stream
responses: {}
summary: 获取文件
tags:
- File
/file/path:
get:
consumes:
- application/json
description: 获取文件路径
parameters:
- description: 路径
example: C:\Windows\System32\cmd.exe
in: query
name: path
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
type: string
type: object
default:
description: ""
schema:
$ref: '#/definitions/errorx.CodeErrorResponse'
summary: 获取文件路径
tags:
- File
/wol/wakeup:
post:
consumes:
- application/json
description: wol 唤醒
parameters:
- description: 局域网唤醒
in: body
name: vo
required: true
schema:
$ref: '#/definitions/controller.WakeupDTO'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Response'
- properties:
data:
type: string
type: object
default:
description: ""
schema:
$ref: '#/definitions/errorx.CodeErrorResponse'
summary: wol 唤醒
tags:
- Wol
swagger: "2.0"

10
pkg/server/init.go Normal file
View File

@ -0,0 +1,10 @@
package server
//go:generate go version
//go:generate go env -w GO111MODULE=on
//go:generate go env -w GOPROXY=https://goproxy.cn,direct
//go:generate go install github.com/swaggo/swag/cmd/swag@latest
//go:generate go get -u xorm.io/reverse
//go:generate go install xorm.io/reverse
//go:generate go mod tidy
//go:generate swag init -pd ./

70
pkg/server/main.go Normal file
View File

@ -0,0 +1,70 @@
package server
import (
"context"
"fmt"
"go.uber.org/zap/zapcore"
"log"
"net/http"
"os"
"os/signal"
_ "skapp/pkg/server/docs"
"skapp/pkg/config/toml"
"skapp/pkg/global"
"skapp/pkg/logger"
"time"
)
// @title Wails AssetServer API
// @version 1.0
// @description Wails AssetServer API 文档
// @termsOfService http://swagger.io/terms/
// @contact.name Shikong
// @contact.email 919411476@qq.com
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @BasePath /s
func Main() {
conf, err := toml.LoadConfig()
if err != nil {
logger.Log.Fatalf("%s", err)
}
global.Config = conf
if conf.HasDebug() && conf.Debug.Enable {
logger.SetLevel(zapcore.DebugLevel)
} else {
logger.SetLevel(zapcore.ErrorLevel)
}
engine := Server(conf)
srv := &http.Server{
Addr: fmt.Sprintf("%s:%d", conf.Server.Host, conf.Server.Port),
Handler: engine,
}
go func() {
logger.Log.Infof("服务启动 %s", fmt.Sprintf("%s:%d", conf.Server.Host, conf.Server.Port))
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
logger.Log.Info("开始关闭服务")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("服务关闭异常:", err)
}
logger.Log.Info("服务关闭完成")
}

View File

@ -0,0 +1,14 @@
package middleware
import (
"github.com/gin-gonic/gin"
)
func CorsMiddleWare() gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.Header("Access-Control-Allow-Origin", "*")
ctx.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
ctx.Header("Access-Control-Allow-Headers", "Action, Module, X-PINGOTHER, Content-Type, Content-Disposition")
ctx.Next()
}
}

View File

@ -0,0 +1,39 @@
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
"runtime/debug"
"skapp/pkg/logger"
"skapp/pkg/utils/errorx"
)
// ErrorHandler
// gin 全局异常处理
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
err := parseError(r)
//打印错误堆栈信息
logger.Log.Error(err)
debug.PrintStack()
//封装通用JSON返回
c.JSON(http.StatusOK, errorx.NewDefaultError(err))
//终止后续接口调用不加的话recover到异常后还会继续执行接口里后续代码
c.Abort()
}
}()
//加载完 defer recover继续后续的插件及代码执行
c.Next()
}
}
func parseError(r interface{}) string {
switch v := r.(type) {
case error:
return v.Error()
default:
return r.(string)
}
}

View File

@ -0,0 +1,26 @@
package router
import (
"github.com/gin-gonic/gin"
"skapp/pkg/utils/errorx"
)
// ErrorHandler
//
// @Summary 错误处理
// @Description 错误信息示例
// @Tags Error
// @Accept json
// @Produce json
// @Failure default {object} errorx.CodeErrorResponse
// @Router /error [get]
// @Router /error [post]
// @Router /error [put]
// @Router /error [delete]
// @Router /error [patch]
func setupErrorRouter(r *gin.RouterGroup) {
r.Any("/error", func(ctx *gin.Context) {
ctx.JSON(200, errorx.NewDefaultError("错误信息示例"))
})
}

View File

@ -0,0 +1,36 @@
package router
import (
"github.com/gin-gonic/gin"
"skapp/pkg/server/controller"
"skapp/pkg/config"
_ "skapp/pkg/server/docs"
)
func SetupRouter(conf *config.Config, g *gin.RouterGroup) {
setupConfigRouter(g)
setupFileRouter(g)
setupWolRouter(g)
setupErrorRouter(g)
if conf.HasDebug() && conf.Debug.Enable {
setupSwaggerRouter(g)
}
}
func setupConfigRouter(g *gin.RouterGroup) {
conf := g.Group("/config")
conf.POST("/", controller.PostConfig)
}
func setupFileRouter(g *gin.RouterGroup) {
file := g.Group("/file")
file.GET("/", controller.GetFile)
file.GET("/path", controller.GetPath)
}
func setupWolRouter(g *gin.RouterGroup) {
file := g.Group("/wol")
file.POST("/wakeup", controller.PostWolWakeUP)
}

View File

@ -0,0 +1,11 @@
package router
import (
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
func setupSwaggerRouter(r *gin.RouterGroup) {
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}

28
pkg/server/server.go Normal file
View File

@ -0,0 +1,28 @@
package server
import (
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
"skapp/pkg/config"
"skapp/pkg/server/middleware"
"skapp/pkg/server/router"
)
func Server(config *config.Config) (engine *gin.Engine) {
if config.HasDebug() && config.Debug.Enable {
gin.SetMode(gin.DebugMode)
engine = gin.Default()
} else {
gin.SetMode(gin.ReleaseMode)
engine = gin.New()
}
engine.Use(gzip.Gzip(gzip.DefaultCompression))
engine.Use(middleware.CorsMiddleWare())
engine.Use(middleware.ErrorHandler())
g := engine.RouterGroup.Group("/s")
router.SetupRouter(config, g)
return engine
}

View File

@ -0,0 +1,87 @@
package wol
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"net"
"regexp"
"strings"
)
var (
MacFormatError = errors.New("mac 地址格式错误")
)
type Service struct {
}
var Services *Service
func init() {
Services = &Service{}
}
func (s *Service) validateMac(mac string) bool {
exp, _ := regexp.Compile("([0-9a-fA-F]{2}(-|:)?){5}([a-fA-F0-9]{2})")
macLen := len(mac)
return exp.MatchString(mac) && (macLen == 12 || macLen == 17)
}
func (s *Service) WakeUp(mac string, port int) error {
mac = strings.Replace(strings.Replace(mac, ":", "", -1), "-", "", -1)
if !s.validateMac(mac) {
return MacFormatError
}
return s.wake(mac, port)
}
func (s *Service) wake(mac string, port int) error {
udpAddr, _ := net.ResolveUDPAddr("udp", fmt.Sprintf("255.255.255.255:%d", port))
interfaces, err := net.Interfaces()
if err != nil {
return err
}
macHex, _ := hex.DecodeString(mac)
// 广播MAC地址 FF:FF:FF:FF:FF:FF
var broadcast = []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
var buffer bytes.Buffer
buffer.Write(broadcast)
for i := 0; i < 16; i++ {
buffer.Write(macHex)
}
wolPackage := buffer.Bytes()
for _, i := range interfaces {
if i.Flags&net.FlagUp == 0 {
continue
}
addrs, err := i.Addrs()
if err != nil {
return err
}
for _, addr := range addrs {
if ip, ok := addr.(*net.IPNet); ok {
if ipv4 := ip.IP.To4(); ipv4 != nil {
conn, err := net.DialUDP("udp", &net.UDPAddr{IP: ipv4}, udpAddr)
if err != nil {
return err
}
_, _ = conn.Write(wolPackage)
_ = conn.Close()
}
}
}
}
return err
}

View File

@ -0,0 +1,92 @@
package wol
import (
"bytes"
"encoding/hex"
"net"
"regexp"
"strings"
"testing"
)
func TestRegExp(t *testing.T) {
validateMac := func() func(mac string) bool {
exp, _ := regexp.Compile("([0-9a-fA-F]{2}(-|:)?){5}([a-fA-F0-9]{2})")
return func(mac string) bool {
macLen := len(mac)
return exp.MatchString(mac) && (macLen == 12 || macLen == 17)
}
}()
t.Logf("255.255.255.255 %t", validateMac("255.255.255.255"))
t.Logf("FF:FF:FF:FF:FF:FF %t", validateMac("FF:FF:FF:FF:FF:FF"))
t.Logf("00-E0-4C-84-50-EB %t", validateMac("00-E0-4C-84-50-EB"))
t.Logf("00E04C8450EB %t", validateMac("00E04C8450EB"))
t.Logf("00E04C8450EBA %t", validateMac("00E04C8450EBA"))
}
func TestWol(t *testing.T) {
udpAddr, err := net.ResolveUDPAddr("udp", "255.255.255.255:9")
if err != nil {
t.Fatal(err)
}
t.Logf("%v", udpAddr)
interfaces, err := net.Interfaces()
if err != nil {
t.Fatal(err)
}
mac := "00-E0-4C-84-50-EB"
mac = strings.Replace(strings.Replace(mac, ":", "", -1), "-", "", -1)
macHex, _ := hex.DecodeString(mac)
// 广播MAC地址 FF:FF:FF:FF:FF:FF
var broadcast = []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
var buffer bytes.Buffer
buffer.Write(broadcast)
for i := 0; i < 16; i++ {
buffer.Write(macHex)
}
wolPackage := buffer.Bytes()
for _, i := range interfaces {
if i.Flags&net.FlagUp == 0 {
t.Logf("%s 未启用", i.Name)
continue
}
t.Logf("%s 已启用", i.Name)
addrs, err := i.Addrs()
if err != nil {
t.Fatal(err)
}
for _, addr := range addrs {
if ip, ok := addr.(*net.IPNet); ok {
if ipv4 := ip.IP.To4(); ipv4 != nil {
t.Logf("\t ipv4: %s", ipv4.String())
conn, err := net.DialUDP("udp", &net.UDPAddr{IP: ipv4}, udpAddr)
defer func() {
_ = conn.Close()
}()
if err != nil {
t.Errorf("wol 幻数据包发送失败 %s", err)
}
_, _ = conn.Write(wolPackage)
t.Logf("wol 幻数据包发送成功, %s %s %s", i.Name, ipv4.String(), mac)
}
}
}
}
}
func TestWOLService(t *testing.T) {
var service *Service
err := service.WakeUp("00-E0-4C-84-50-EB", 9)
if err != nil {
t.Fatal(err)
}
t.Log("wol 唤醒成功")
}

View File

@ -0,0 +1,59 @@
package errorx
import (
"skapp/pkg/utils/response"
)
type CodeError struct {
*response.Response
}
type CodeErrorResponse struct {
*response.Response
}
func NewCustomError(code int, data interface{}, msg string) error {
return &CodeError{
Response: response.NewCustomResponse(code, data, msg),
}
}
func NewErrorWithCode(code int, msg string) error {
return &CodeError{
Response: response.NewCustomResponse(code, nil, msg),
}
}
func ParseError(err error) error {
if err != nil {
return NewErrorWithCode(response.ERROR, err.Error())
}
return nil
}
func ParseErrorWithCode(code int, err error) error {
if err != nil {
return NewErrorWithCode(code, err.Error())
}
return nil
}
func NewDefaultError(msg string) error {
return NewErrorWithCode(response.ERROR, msg)
}
func (e *CodeError) Error() string {
return e.Msg
}
func (e *CodeError) Resp() *CodeErrorResponse {
return &CodeErrorResponse{
Response: &response.Response{
Code: e.Code,
Data: e.Data,
Msg: e.Msg,
},
}
}

View File

@ -0,0 +1,20 @@
package app
import (
"github.com/sony/sonyflake"
"time"
)
const DefaultSonyFlakeStartTime = "2022-01-01"
var SonyFlake *sonyflake.Sonyflake
func init() {
SonyFlake = initSonyFlake()
}
func initSonyFlake() *sonyflake.Sonyflake {
var s sonyflake.Settings
s.StartTime, _ = time.Parse("2006-01-02", DefaultSonyFlakeStartTime)
return sonyflake.NewSonyflake(s)
}

10
pkg/utils/json/json.go Normal file
View File

@ -0,0 +1,10 @@
package json
import (
"github.com/goccy/go-json"
)
func Json(data interface{}) string {
jsonBytes, _ := json.MarshalIndent(data, "", " ")
return string(jsonBytes)
}

View File

@ -0,0 +1,27 @@
package resources
import (
"embed"
"io/fs"
"path"
)
type RelativeFS struct {
Fs embed.FS
Root string
}
func (r *RelativeFS) Open(name string) (fs.File, error) {
p := path.Join(r.Root, name)
return r.Fs.Open(p)
}
func (r *RelativeFS) ReadDir(name string) ([]fs.DirEntry, error) {
p := path.Join(r.Root, name)
return r.Fs.ReadDir(p)
}
func (r *RelativeFS) ReadFile(name string) ([]byte, error) {
p := path.Join(r.Root, name)
return r.Fs.ReadFile(p)
}

View File

@ -0,0 +1,40 @@
package response
type Response struct {
Code Code `json:"code" example:"200"`
Data interface{} `json:"data"`
Msg string `json:"msg" example:"OK"`
}
type Code = int
const (
SUCCESS = 200
UNAUTHORIZED = 401
FORBIDDEN = 403
ERROR = 500
)
func NewCustomResponse(code int, data interface{}, msg string) *Response {
return &Response{
Code: code,
Data: data,
Msg: msg,
}
}
func NewResponseWithCode(code int, data interface{}) *Response {
return &Response{
Code: SUCCESS,
Data: data,
Msg: "OK",
}
}
func NewResponse(data interface{}) *Response {
return &Response{
Code: SUCCESS,
Data: data,
Msg: "OK",
}
}

31
pkg/utils/time/time.go Normal file
View File

@ -0,0 +1,31 @@
package time
import (
"fmt"
"time"
)
type Time time.Time
const (
timeFormat = "2006-01-02 15:04:05"
)
// MarshalJSON on Json Time format Time field with %Y-%m-%d %H:%M:%S
func (t *Time) MarshalJSON() ([]byte, error) {
// 重写time转换成json之后的格式
var tmp = fmt.Sprintf("\"%s\"", time.Time(*t).Format(timeFormat))
return []byte(tmp), nil
}
func (t *Time) UnmarshalJSON(data []byte) error {
// Ignore null, like in the main JSON package.
if string(data) == "null" {
return nil
}
// Fractional seconds are handled implicitly by Parse.
var err error
rawT, err := time.Parse(`"`+timeFormat+`"`, string(data))
*t = Time(rawT)
return err
}

View File

@ -0,0 +1,44 @@
package validator
import (
"errors"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
"reflect"
)
var zhLocales = zh.New()
var validate = validator.New()
var uni = ut.New(zhLocales)
var trans, _ = uni.GetTranslator("zh")
// 验证器注册翻译器
var _ = zhTranslations.RegisterDefaultTranslations(validate, trans)
func init() {
//注册一个函数获取struct tag里自定义的label作为字段名
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
if label := fld.Tag.Get("label"); len(label) > 0 {
return label
}
if jsonField := fld.Tag.Get("json"); len(jsonField) > 0 {
return jsonField
}
return fld.Name
})
}
func ValidateStruct(data interface{}) (err error) {
err = validate.Struct(data)
if err != nil {
for _, e := range err.(validator.ValidationErrors) {
err = errors.New(e.Translate(trans))
break
}
}
return
}