go 权限 + websokets
This commit is contained in:
parent
05a5942fca
commit
e68e01edc9
|
@ -21,3 +21,4 @@
|
|||
# Go workspace file
|
||||
go.work
|
||||
|
||||
dist/node_modules/**
|
|
@ -0,0 +1,45 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"github.com/Fromsko/gouitls/logs"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var echo *logrus.Logger
|
||||
|
||||
func init() {
|
||||
echo = logs.InitLogger()
|
||||
}
|
||||
|
||||
func Info() func(...any) {
|
||||
// for _, arg := range args {
|
||||
// echo.Info(arg)
|
||||
// }
|
||||
return echo.Info
|
||||
}
|
||||
|
||||
func Infof(k string, args ...any) {
|
||||
echo.Infof(k, args...)
|
||||
}
|
||||
|
||||
func Debug(args ...any) {
|
||||
for _, arg := range args {
|
||||
echo.Debug(arg)
|
||||
}
|
||||
}
|
||||
|
||||
func Warn(args ...any) {
|
||||
for _, arg := range args {
|
||||
echo.Warn(arg)
|
||||
}
|
||||
}
|
||||
|
||||
func Error(args ...any) {
|
||||
for _, arg := range args {
|
||||
echo.Error(arg)
|
||||
}
|
||||
}
|
||||
|
||||
func Errorf(f string, args ...any) {
|
||||
echo.Errorf(f, args...)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package tools
|
||||
|
||||
// 快速检测[指定字段]是否包含在哈希表中
|
||||
func IsKeyInMap(key []string, dict any) bool {
|
||||
for _, k := range key {
|
||||
if _, ok := dict.(map[string]any)[k]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
d := map[string]any{
|
||||
"a": "",
|
||||
"b": "",
|
||||
"c": "",
|
||||
}
|
||||
fmt.Println(IsKeyInMap([]string{"a", "d"}, d))
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,23 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
host_ip=$(cat /etc/resolv.conf |grep "nameserver" |cut -f 2 -d " ")
|
||||
export ALL_PROXY="http://$host_ip:7890"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"emmet.includeLanguages.vue": {
|
||||
"vue": "html",
|
||||
"vuex": "html",
|
||||
"javascript": "html"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 skong
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,18 @@
|
|||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||
|
||||
1. Disable the built-in TypeScript Extension
|
||||
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
|
@ -0,0 +1,9 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ChatBox: typeof import('./src/components/ChatBox.vue')['default']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElLi: typeof import('element-plus/es')['ElLi']
|
||||
ElUl: typeof import('element-plus/es')['ElUl']
|
||||
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
|
||||
LogCard: typeof import('./src/components/LogCard.vue')['default']
|
||||
Login: typeof import('./src/components/Login.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vInfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll']
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "mind-map",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^20.11.5",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"axios": "^1.6.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.8",
|
||||
"vue": "^3.3.11",
|
||||
"vue-tsc": "^1.8.25",
|
||||
"websocket": "^1.0.34"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.5",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"typescript": "^5.2.2",
|
||||
"unplugin-auto-import": "^0.17.3",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.0.8",
|
||||
"vue-tsc": "^1.8.25"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<LogCard />
|
||||
<ChatBox />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import ChatBox from './components/ChatBox.vue'
|
||||
import LogCard from './components/LogCard.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
LogCard,
|
||||
ChatBox,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
/* display: flex; */
|
||||
/* justify-content: center; */
|
||||
/* align-items: center; */
|
||||
background: linear-gradient(50deg, #75accb, #cce4d7);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
After Width: | Height: | Size: 496 B |
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div class="chat-box">
|
||||
<h2>发送消息</h2>
|
||||
<el-input v-model="message" placeholder="输入消息..." />
|
||||
<el-button @click="sendMessage" type="primary">发送</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const message = ref('')
|
||||
|
||||
function sendMessage() {
|
||||
// 在这里添加WebSocket发送消息的逻辑
|
||||
if (message.value.trim() !== '') {
|
||||
// 发送消息
|
||||
message.value = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-box {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,38 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Install
|
||||
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
|
||||
in your IDE for a better DX
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,192 @@
|
|||
<template>
|
||||
<div class="log-container">
|
||||
<h2>实时日志</h2>
|
||||
<ElCard class="log-card">
|
||||
<ul class="infinite-list" style="overflow: auto">
|
||||
<li
|
||||
v-for="(log, index) in logs"
|
||||
:key="index"
|
||||
class="infinite-list-item"
|
||||
>
|
||||
类型: {{ log.type }} 消息: {{ log.msg }}
|
||||
</li>
|
||||
</ul>
|
||||
</ElCard>
|
||||
|
||||
<ElCard class="send-card">
|
||||
<h3>发送消息</h3>
|
||||
|
||||
<ElCard>
|
||||
<div class="input-group">
|
||||
<label for="type">type:</label>
|
||||
<ElInput id="type" v-model="message.type" />
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="msg">msg:</label>
|
||||
<ElInput id="msg" v-model="message.msg" @keydown.enter="sendData" />
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
<div class="button-group">
|
||||
<ElButton
|
||||
type="primary"
|
||||
:disabled="Boolean(!socket || !message.msg)"
|
||||
@click="sendData"
|
||||
>
|
||||
发送数据
|
||||
</ElButton>
|
||||
<ElButton type="success" :disabled="Boolean(socket)" @click="startConn"
|
||||
>启动连接</ElButton
|
||||
>
|
||||
<ElButton type="danger" :disabled="Boolean(!socket)" @click="closeConn"
|
||||
>关闭连接</ElButton
|
||||
>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElNotification } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const logs = ref<{ type: number; msg: string }[]>([])
|
||||
const socket = ref<WebSocket | null>(null)
|
||||
const message = ref<{ type: string; msg: string }>({ type: 'ping', msg: '' })
|
||||
|
||||
const sendData = () => {
|
||||
if (!message.value.type || !message.value.msg) {
|
||||
ElNotification({
|
||||
title: '错误',
|
||||
message: '请填写完整的类型和消息',
|
||||
type: 'error',
|
||||
position: 'top-right',
|
||||
duration: 3000,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const jsonData = JSON.stringify(message.value)
|
||||
|
||||
if (socket.value) {
|
||||
socket.value.send(jsonData)
|
||||
message.value.msg = ''
|
||||
}
|
||||
}
|
||||
|
||||
let closeConn = () => {
|
||||
if (socket.value) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const startConn = () => {
|
||||
socket.value = new WebSocket('ws://localhost:9090/api/ws/')
|
||||
|
||||
socket.value.onopen = () => {
|
||||
ElNotification({
|
||||
title: 'WebSocket连接已建立!',
|
||||
type: 'success',
|
||||
position: 'top-right',
|
||||
duration: 3000,
|
||||
})
|
||||
}
|
||||
|
||||
socket.value.onmessage = (event) => {
|
||||
const log = JSON.parse(event.data)
|
||||
|
||||
logs.value.push(log)
|
||||
|
||||
if (log.type != 'ping') {
|
||||
ElNotification({
|
||||
title: '新的日志',
|
||||
message: log.msg,
|
||||
type: 'success',
|
||||
position: 'top-right',
|
||||
duration: 3000,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
socket.value.onclose = () => {
|
||||
ElNotification({
|
||||
title: '连接已经关闭!',
|
||||
type: 'success',
|
||||
position: 'top-right',
|
||||
duration: 3000,
|
||||
})
|
||||
}
|
||||
|
||||
socket.value.onerror = (error) => {
|
||||
console.error('WebSocket连接发生错误:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.log-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.log-card {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
/* width: 35vw; */
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.send-card {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
width: 420px;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.infinite-list {
|
||||
height: 150px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.infinite-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 35px;
|
||||
margin: 3px;
|
||||
color: var(--el-color-primary);
|
||||
background: linear-gradient(50deg, #75accb, #cce4d7);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,185 @@
|
|||
<template>
|
||||
<div class="login-box">
|
||||
<div class="owl" :class="{ password: isPasswordFocused }">
|
||||
<div class="hand"></div>
|
||||
<div class="hand hand-r"></div>
|
||||
<div class="arms">
|
||||
<div class="arm"></div>
|
||||
<div class="arm arm-r"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-box">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="账号"
|
||||
v-model="userAuth.account"
|
||||
:disabled="userAuth.isInput"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
v-model="userAuth.password"
|
||||
:disabled="userAuth.isInput"
|
||||
@focus="handlePasswordFocus"
|
||||
@blur="handlePasswordBlur"
|
||||
@keyup.enter="handleLogin"
|
||||
/>
|
||||
<button @click="handleLogin" :disabled="userAuth.isInput">登录</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
let userAuth = reactive({
|
||||
account: '',
|
||||
password: '',
|
||||
isInput: false,
|
||||
})
|
||||
|
||||
let isPasswordFocused = ref(false)
|
||||
|
||||
let handlePasswordFocus = () => {
|
||||
isPasswordFocused.value = true
|
||||
}
|
||||
|
||||
let handlePasswordBlur = () => {
|
||||
isPasswordFocused.value = false
|
||||
}
|
||||
|
||||
let handleLogin = () => {
|
||||
if (!userAuth.isInput) {
|
||||
userAuth.isInput = true
|
||||
// 模拟登录逻辑
|
||||
setTimeout(() => {
|
||||
console.log('登录成功,重定向到其他页面')
|
||||
userAuth.isInput = false
|
||||
}, 2000)
|
||||
} else {
|
||||
userAuth.isInput = false
|
||||
console.log('登录失败,重新启用按钮和回车键')
|
||||
}
|
||||
console.log(userAuth.isInput)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-box {
|
||||
/* 相对定位 */
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 320px;
|
||||
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 10px;
|
||||
border-radius: 15px;
|
||||
background: linear-gradient(200deg, #72afd3, #96fbc4);
|
||||
justify-content: center;
|
||||
}
|
||||
.input-box {
|
||||
/* 弹性布局 垂直排列 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.input-box input {
|
||||
height: 40px;
|
||||
border-radius: 3px;
|
||||
/* 缩进15像素 */
|
||||
text-indent: 15px;
|
||||
outline: none;
|
||||
border: none;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.input-box input:focus {
|
||||
outline: 1px solid rgb(36, 197, 189);
|
||||
}
|
||||
|
||||
.input-box button {
|
||||
width: 9em;
|
||||
height: 3em;
|
||||
border: none;
|
||||
border-radius: 30em;
|
||||
font-size: 15px;
|
||||
font-family: inherit;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
background-color: lightseagreen;
|
||||
margin: 0 auto; /* 水平居中 */
|
||||
display: block; /* 使按钮成为块级元素 */
|
||||
transition: background-color 0.3s ease; /* 添加简单的背景色过渡效果 */
|
||||
}
|
||||
|
||||
.input-box button:hover {
|
||||
background-color: #3cb371; /* 悬停时的背景色 */
|
||||
}
|
||||
|
||||
/* 接下来是猫头鹰的样式 */
|
||||
.owl {
|
||||
width: 211px;
|
||||
height: 108px;
|
||||
/* 背景图片 */
|
||||
background: url('@/assets/owl-login.png') no-repeat;
|
||||
/* 绝对定位 */
|
||||
position: absolute;
|
||||
top: -100px;
|
||||
/* 水平居中 */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.owl .hand {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 40px;
|
||||
background-color: #472d20;
|
||||
/* 绝对定位 */
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
bottom: -8px;
|
||||
/* 沿Y轴缩放0.6倍(压扁) */
|
||||
transform: scaleY(0.6);
|
||||
/* 动画过渡 */
|
||||
transition: 0.3s ease-out;
|
||||
}
|
||||
.owl .hand.hand-r {
|
||||
left: 170px;
|
||||
}
|
||||
.owl.password .hand {
|
||||
transform: translateX(42px) translateY(-15px) scale(0.7);
|
||||
}
|
||||
.owl.password .hand.hand-r {
|
||||
transform: translateX(-42px) translateY(-15px) scale(0.7);
|
||||
}
|
||||
.owl .arms {
|
||||
position: absolute;
|
||||
top: 58px;
|
||||
width: 100%;
|
||||
height: 41px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.owl .arms .arm {
|
||||
width: 40px;
|
||||
height: 65px;
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 40px;
|
||||
background: url('@/assets/owl-login-arm.png') no-repeat;
|
||||
transform: rotate(-20deg);
|
||||
transition: 0.3s ease-out;
|
||||
}
|
||||
.owl .arms .arm.arm-r {
|
||||
transform: rotate(20deg) scaleX(-1);
|
||||
left: 158px;
|
||||
}
|
||||
.owl.password .arms .arm {
|
||||
transform: translateY(-40px) translateX(40px);
|
||||
}
|
||||
.owl.password .arms .arm.arm-r {
|
||||
transform: translateY(-40px) translateX(-40px) scaleX(-1);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,10 @@
|
|||
// main.ts
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(ElementPlus)
|
||||
app.mount('#app')
|
|
@ -0,0 +1,8 @@
|
|||
/// <reference types="vite/client" />
|
||||
declare module "*.vue" {
|
||||
import type { DefineComponent } from "vue";
|
||||
|
||||
const vueComponent: DefineComponent<{}, {}, any>;
|
||||
|
||||
export default vueComponent;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
/* Elm */
|
||||
"types": ["element-plus/global"],
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import vue from '@vitejs/plugin-vue';
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import AutoImport from 'unplugin-auto-import/vite';
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['element-plus'] // 添加需要预打包的依赖项
|
||||
}
|
||||
})
|
|
@ -0,0 +1,41 @@
|
|||
module go-auth
|
||||
|
||||
go 1.21.5
|
||||
|
||||
require github.com/sirupsen/logrus v1.9.3
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.10.2 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.17.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Fromsko/gouitls v1.2.7
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
)
|
|
@ -0,0 +1,103 @@
|
|||
github.com/Fromsko/gouitls v1.2.7 h1:clBl9mvELvV0ls6mxhqFPLDhJb5J3Db4+beR5TL93UY=
|
||||
github.com/Fromsko/gouitls v1.2.7/go.mod h1:pnx9wA17MZUcP8T93DL+CH++RPCVX/SVByCJjW5mJO0=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
||||
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
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/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
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/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.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
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.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.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
|
||||
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/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.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
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.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.2/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/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/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
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/sys v0.0.0-20220715151400-c0bba94af5f8/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/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"go-auth/routers"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
engine := gin.New()
|
||||
|
||||
routers.InitRouters(engine)
|
||||
|
||||
engine.Run(":9090")
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package base
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
/*
|
||||
定义基础中间件
|
||||
*/
|
||||
|
||||
// Cors 跨域请求
|
||||
func Cors() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
method := c.Request.Method
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
if origin != "" {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
|
||||
c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
|
||||
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
if method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// Add 函数用于计算两个整数的和
|
||||
//
|
||||
// 参数:
|
||||
// - a: 第一个整数
|
||||
// - b: 第二个整数
|
||||
//
|
||||
// 返回:
|
||||
// - sum: 两个整数的和
|
||||
func Add(a, b int) int {
|
||||
router := gin.New()
|
||||
|
||||
router.Use(Cors())
|
||||
|
||||
return a + b
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package class
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func Register(api *gin.RouterGroup) {
|
||||
user := api.Group("/user")
|
||||
|
||||
_ = user
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package routers
|
||||
|
||||
import (
|
||||
"go-auth/routers/class"
|
||||
"go-auth/routers/stream"
|
||||
"go-auth/routers/user"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// InitRouters 注册基础路由
|
||||
func InitRouters(engine *gin.Engine) {
|
||||
|
||||
v1 := engine.Group("/api/v1")
|
||||
{
|
||||
user.Register(v1)
|
||||
class.Register(v1)
|
||||
}
|
||||
|
||||
ws := engine.Group("/api/ws/")
|
||||
{
|
||||
pool := stream.NewConnectionPool(
|
||||
stream.WithConnected(nil),
|
||||
stream.WithExamStart(nil),
|
||||
stream.WithExamStop(nil),
|
||||
stream.WithDestroy(nil),
|
||||
)
|
||||
ws.GET("", stream.MonitorWS(pool)) // 创建WebSocket连接池
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package stream
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// registerEvent 注册事件
|
||||
func (pool *ConnectionPool) registerEvent(eventName string, handler ConnEvent) {
|
||||
pool.Events[eventName] = append(pool.Events[eventName], handler)
|
||||
}
|
||||
|
||||
// triggerEvent 事件触发
|
||||
func (pool *ConnectionPool) triggerEvent(event string, connection *Connection, data gin.H) {
|
||||
if handlers, ok := pool.Events[event]; ok {
|
||||
for _, handler := range handlers {
|
||||
handler(connection, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// defaultEvent 默认事件
|
||||
func (pool *ConnectionPool) defaultEvent(eventName string) {
|
||||
pool.Events[eventName] = append(pool.Events[eventName], func(obj *Connection, data gin.H) {
|
||||
log.Infof("Events: [%s]", eventName)
|
||||
})
|
||||
}
|
||||
|
||||
// WithExamStart 考试开始
|
||||
func WithExamStart(event ConnEvent) PoolOption {
|
||||
if event == nil {
|
||||
event = func(obj *Connection, data gin.H) {
|
||||
log.Info("考试开始")
|
||||
}
|
||||
}
|
||||
return func(cp *ConnectionPool) {
|
||||
cp.registerEvent("exam_start", event)
|
||||
}
|
||||
}
|
||||
|
||||
// WithExamStop 考试结束
|
||||
func WithExamStop(event ConnEvent) PoolOption {
|
||||
if event == nil {
|
||||
event = func(obj *Connection, data gin.H) {
|
||||
log.Info("考试结束")
|
||||
}
|
||||
}
|
||||
return func(cp *ConnectionPool) {
|
||||
cp.registerEvent("exam_stop", event)
|
||||
}
|
||||
}
|
||||
|
||||
// WithConnected 连接成功
|
||||
func WithConnected(event ConnEvent) PoolOption {
|
||||
if event == nil {
|
||||
event = func(obj *Connection, data gin.H) {
|
||||
log.Info("连接成功")
|
||||
}
|
||||
}
|
||||
return func(cp *ConnectionPool) {
|
||||
cp.registerEvent("connected", event)
|
||||
}
|
||||
}
|
||||
|
||||
// WithDestroy 连接注销
|
||||
func WithDestroy(event ConnEvent) PoolOption {
|
||||
if event == nil {
|
||||
event = func(obj *Connection, data gin.H) {
|
||||
log.Info("连接注销")
|
||||
}
|
||||
}
|
||||
return func(cp *ConnectionPool) {
|
||||
cp.registerEvent("destroy", event)
|
||||
}
|
||||
}
|
||||
|
||||
// WithNotify 通知
|
||||
func WithNotify(event ConnEvent) PoolOption {
|
||||
if event == nil {
|
||||
event = func(obj *Connection, data gin.H) {
|
||||
log.Info("通知")
|
||||
}
|
||||
}
|
||||
return func(cp *ConnectionPool) {
|
||||
cp.registerEvent("notify", event)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package stream
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// NewConnectionPool 初始化一个连接池
|
||||
func NewConnectionPool(opts ...PoolOption) *ConnectionPool {
|
||||
pool := &ConnectionPool{
|
||||
Connections: make(map[*websocket.Conn]*Connection),
|
||||
Broadcast: make(chan gin.H),
|
||||
Register: make(chan *Connection),
|
||||
Unregister: make(chan *Connection),
|
||||
Events: make(map[string][]ConnEvent),
|
||||
PingInterval: 5 * time.Second,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(pool)
|
||||
}
|
||||
|
||||
for _, k := range []string{"connected", "destroy"} {
|
||||
if _, ok := pool.Events[k]; !ok {
|
||||
pool.defaultEvent(k)
|
||||
}
|
||||
}
|
||||
|
||||
return pool
|
||||
}
|
||||
|
||||
func MonitorWS(pool *ConnectionPool) gin.HandlerFunc {
|
||||
// 处理连接池中的连接和事件
|
||||
go HandleConnections(pool)
|
||||
return func(ctx *gin.Context) {
|
||||
ctx.Set("user_id", "111")
|
||||
// 处理WebSocket连接请求
|
||||
handleWebSocket(ctx, pool)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// pool.OnConnected(func(obj *Connection, data gin.H) {
|
||||
// log.Info("is connected")
|
||||
// data["type"] = "ping"
|
||||
// data["msg"] = "ddd"
|
||||
// obj.Conn.WriteJSON(data)
|
||||
// })
|
||||
|
||||
// pool.OnDestory(func(obj *Connection, data gin.H) {
|
||||
// log.Info("被注销了!")
|
||||
// log.Info(len(pool.Connections))
|
||||
// })
|
||||
|
||||
*/
|
|
@ -0,0 +1,164 @@
|
|||
package stream
|
||||
|
||||
/*
|
||||
定义 websoket MonitorWS
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Fromsko/gouitls/logs"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// 定义 WebSocket 连接对象
|
||||
type Connection struct {
|
||||
UserID string
|
||||
Role string
|
||||
Conn *websocket.Conn
|
||||
}
|
||||
|
||||
// 定义 WebSocket 连接事件处理
|
||||
type ConnEvent func(obj *Connection, data gin.H)
|
||||
|
||||
// 连接池可选参数
|
||||
type PoolOption func(*ConnectionPool)
|
||||
|
||||
// 日志
|
||||
var log = logs.InitLogger()
|
||||
|
||||
// 定义 WebSocket 连接池
|
||||
type ConnectionPool struct {
|
||||
Connections map[*websocket.Conn]*Connection
|
||||
Broadcast chan gin.H
|
||||
Register chan *Connection
|
||||
Unregister chan *Connection
|
||||
Events map[string][]ConnEvent
|
||||
PingInterval time.Duration
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// 处理WebSocket连接请求
|
||||
func handleWebSocket(ctx *gin.Context, pool *ConnectionPool) {
|
||||
// 升级HTTP连接为WebSocket连接
|
||||
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// 处理连接请求
|
||||
// 从数据库中获取用户信息,包括UserID和Role
|
||||
// 如果信息不存在或者不符合要求,则拒绝连接
|
||||
|
||||
_, ok := ctx.Get("user_id")
|
||||
if !ok {
|
||||
log.Error("非法连接")
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// 创建连接对象
|
||||
connection := &Connection{
|
||||
UserID: "", //userId.(string),
|
||||
Role: "", // ctx.MustGet("role").(string)
|
||||
Conn: conn,
|
||||
}
|
||||
|
||||
// 将连接对象注册到连接池中
|
||||
pool.Register <- connection
|
||||
|
||||
// 处理连接断开事件
|
||||
defer func() {
|
||||
pool.Unregister <- connection
|
||||
}()
|
||||
|
||||
// 处理接收到的消息
|
||||
for {
|
||||
// 检查连接是否已关闭
|
||||
if _, r, err := conn.NextReader(); err != nil {
|
||||
// 判断错误类型是否为websocket: close 1001 (going away)
|
||||
if websocket.IsCloseError(err, websocket.CloseGoingAway) {
|
||||
log.Info("连接已关闭")
|
||||
break
|
||||
}
|
||||
|
||||
if websocket.IsCloseError(err, websocket.CloseNoStatusReceived) {
|
||||
log.Info("连接已关闭")
|
||||
break
|
||||
}
|
||||
log.Errorf("处理接收到的消息错误: %s", err)
|
||||
break
|
||||
} else {
|
||||
var message gin.H
|
||||
err := json.NewDecoder(r).Decode(&message)
|
||||
if err != nil {
|
||||
log.Info(err)
|
||||
log.Errorf("处理接收到的消息错误: %s", err)
|
||||
break
|
||||
}
|
||||
log.Info(message)
|
||||
|
||||
// 示例:将消息发送给所有连接
|
||||
pool.Broadcast <- message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理连接池中的连接和事件
|
||||
func HandleConnections(pool *ConnectionPool) {
|
||||
for {
|
||||
select {
|
||||
// 处理连接注册事件
|
||||
case connection := <-pool.Register:
|
||||
pool.Connections[connection.Conn] = connection
|
||||
|
||||
// 触发连接加入事件
|
||||
pool.triggerEvent("connected", connection, gin.H{})
|
||||
|
||||
// 处理连接断开事件
|
||||
case connection := <-pool.Unregister:
|
||||
if _, ok := pool.Connections[connection.Conn]; ok {
|
||||
delete(pool.Connections, connection.Conn)
|
||||
|
||||
// 触发连接断开事件
|
||||
pool.triggerEvent("destroy", connection, gin.H{})
|
||||
}
|
||||
|
||||
// 处理广播消息
|
||||
case message := <-pool.Broadcast:
|
||||
// 将消息发送给所有连接
|
||||
for conn := range pool.Connections {
|
||||
err := conn.WriteJSON(message)
|
||||
if err != nil {
|
||||
log.Errorf("广播消息错误: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // 注册事件处理函数
|
||||
// func (pool *ConnectionPool) On(event string, handler ConnEvent) {
|
||||
// pool.Events[event] = append(pool.Events[event], handler)
|
||||
// }
|
||||
|
||||
// func (pool *ConnectionPool) OnConnected(handler ConnEvent) {
|
||||
// pool.Events["connected"] = append(pool.Events["connected"], handler)
|
||||
// }
|
||||
|
||||
// func (pool *ConnectionPool) OnDestory(handler ConnEvent) {
|
||||
// pool.Events["destroy"] = append(pool.Events["destroy"], handler)
|
||||
// }
|
|
@ -0,0 +1,9 @@
|
|||
package user
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func Register(api *gin.RouterGroup) {
|
||||
|
||||
|
||||
// _ = user
|
||||
}
|
Loading…
Reference in New Issue