opt: close unused websocket connections

This commit is contained in:
RockYang 2024-04-30 22:54:39 +08:00
parent 7bb76d581c
commit cc1a3ce343
17 changed files with 144 additions and 85 deletions

View File

@ -204,6 +204,7 @@ func needLogin(c *gin.Context) bool {
c.Request.URL.Path == "/api/admin/login" ||
c.Request.URL.Path == "/api/admin/login/captcha" ||
c.Request.URL.Path == "/api/user/register" ||
c.Request.URL.Path == "/api/user/session" ||
c.Request.URL.Path == "/api/chat/history" ||
c.Request.URL.Path == "/api/chat/detail" ||
c.Request.URL.Path == "/api/chat/list" ||

View File

@ -137,6 +137,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
for {
_, msg, err := client.Receive()
if err != nil {
logger.Debugf("close connection: %s", client.Conn.RemoteAddr())
client.Close()
h.App.ChatClients.Delete(sessionId)
h.App.ChatSession.Delete(sessionId)

View File

@ -6,10 +6,11 @@ import (
"chatplus/store"
"errors"
"fmt"
"github.com/imroc/req/v3"
"github.com/shirou/gopsutil/host"
"strings"
"time"
"github.com/imroc/req/v3"
"github.com/shirou/gopsutil/host"
)
type LicenseService struct {
@ -20,7 +21,7 @@ type LicenseService struct {
machineId string
}
func NewLicenseService(server *core.AppServer, levelDB *store.LevelDB) * LicenseService {
func NewLicenseService(server *core.AppServer, levelDB *store.LevelDB) *LicenseService {
var license types.License
var machineId string
_ = levelDB.Get(types.LicenseKey, &license)
@ -60,7 +61,7 @@ func (s *LicenseService) ActiveLicense(license string, machineId string) error {
}
if response.IsErrorState() {
return fmt.Errorf( "发送激活请求失败:%v", response.Status)
return fmt.Errorf("发送激活请求失败:%v", response.Status)
}
if res.Code != types.Success {
@ -119,7 +120,7 @@ func (s *LicenseService) SyncLicense() {
IsActive: true,
}
s.urlWhiteList = res.Data.Urls
logger.Debugf("同步 License 成功:%v\n%v", s.license, s.urlWhiteList)
//logger.Debugf("同步 License 成功:%v\n%v", s.license, s.urlWhiteList)
next:
time.Sleep(time.Second * 10)
}
@ -151,4 +152,4 @@ func (s *LicenseService) IsValidApiURL(uri string) error {
}
}
return fmt.Errorf("当前 API 地址 %s 不在白名单列表当中。", uri)
}
}

View File

@ -66,4 +66,14 @@ html, body {
white-space: nowrap;
}
.van-toast--fail {
background #fef0f0
color #f56c6c
}
.van-toast--success {
background #D6FBCC
color #07C160
}
</style>

View File

@ -9,7 +9,7 @@
.chat-list-wrapper {
padding 10px 0 10px 0
background #F5F5F5;
background var(--van-background);
overflow hidden
@ -81,7 +81,7 @@
.van-theme-dark {
.mobile-chat {
.message-list-box {
.chat-list-wrapper {
background #232425;
}
}

View File

@ -38,11 +38,11 @@
import {ref} from "vue";
import lodash from 'lodash'
import {validateEmail, validateMobile} from "@/utils/validate";
import {ElMessage} from "element-plus";
import {httpGet, httpPost} from "@/utils/http";
import CaptchaPlus from "@/components/CaptchaPlus.vue";
import SlideCaptcha from "@/components/SlideCaptcha.vue";
import {isMobile} from "@/utils/libs";
import {showMessageError, showMessageOK} from "@/utils/dialog";
// eslint-disable-next-line no-undef
const props = defineProps({
@ -66,13 +66,13 @@ const handleRequestCaptCode = () => {
thumbBase64.value = data.thumb
captKey.value = data.key
}).catch(e => {
ElMessage.error('获取人机验证数据失败:' + e.message)
showMessageError('获取人机验证数据失败:' + e.message)
})
}
const handleConfirm = (dots) => {
if (lodash.size(dots) <= 0) {
return ElMessage.error('请进行人机验证再操作')
return showMessageError('请进行人机验证再操作')
}
let dotArr = []
@ -88,14 +88,14 @@ const handleConfirm = (dots) => {
showCaptcha.value = false
sendMsg()
}).catch(() => {
ElMessage.error('人机验证失败')
showMessageError('人机验证失败')
handleRequestCaptCode()
})
}
const loadCaptcha = () => {
if (!validateMobile(props.receiver) && !validateEmail(props.receiver)) {
return ElMessage.error("请输入合法的手机号/邮箱地址")
return showMessageError("请输入合法的手机号/邮箱地址")
}
showCaptcha.value = true
@ -114,7 +114,7 @@ const sendMsg = () => {
canSend.value = false
httpPost('/api/sms/code', {receiver: props.receiver, key: captKey.value, dots: dots.value}).then(() => {
ElMessage.success('验证码发送成功')
showMessageOK('验证码发送成功')
let time = 120
btnText.value = time
const handler = setInterval(() => {
@ -129,7 +129,7 @@ const sendMsg = () => {
}, 1000)
}).catch(e => {
canSend.value = true
ElMessage.error('验证码发送失败:' + e.message)
showMessageError('验证码发送失败:' + e.message)
})
}
@ -145,7 +145,7 @@ const getSlideCaptcha = () => {
bgImg.value = res.data.bgImg
captKey.value = res.data.key
}).catch(e => {
ElMessage.error('获取人机验证数据失败:' + e.message)
showMessageError('获取人机验证数据失败:' + e.message)
})
}

43
web/src/utils/dialog.js Normal file
View File

@ -0,0 +1,43 @@
/**
* Util lib functions
*/
import {showConfirmDialog, showFailToast, showSuccessToast, showToast} from "vant";
import {isMobile} from "@/utils/libs";
import {ElMessage} from "element-plus";
export function showLoginDialog(router) {
showConfirmDialog({
title: '登录',
message:
'此操作需要登录才能进行,前往登录?',
}).then(() => {
router.push("/login")
}).catch(() => {
// on cancel
});
}
export function showMessageOK(message) {
if (isMobile()) {
showSuccessToast(message)
} else {
ElMessage.success(message)
}
}
export function showMessageInfo(message) {
if (isMobile()) {
showToast(message)
} else {
ElMessage.info(message)
}
}
export function showMessageError(message) {
if (isMobile()) {
showFailToast(message)
} else {
ElMessage.error(message)
}
}

View File

@ -330,7 +330,10 @@ onMounted(() => {
});
onUnmounted(() => {
socket.value = null
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
})
//

View File

@ -761,7 +761,10 @@ onMounted(() => {
onUnmounted(() => {
clipboard.value.destroy()
socket.value = null
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
})
//
@ -779,10 +782,6 @@ const initData = () => {
});
}
onUnmounted(() => {
clipboard.value.destroy()
})
const mjPower = ref(1)
const mjActionPower = ref(1)
httpGet("/api/config/get?key=system").then(res => {

View File

@ -616,7 +616,10 @@ onMounted(() => {
onUnmounted(() => {
clipboard.value.destroy()
socket.value = null
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
})

View File

@ -56,13 +56,13 @@
import {ref} from "vue";
import {Lock, UserFilled} from "@element-plus/icons-vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {useRouter} from "vue-router";
import FooterBar from "@/components/FooterBar.vue";
import {isMobile} from "@/utils/libs";
import {checkSession} from "@/action/session";
import {setUserToken} from "@/store/session";
import ResetPass from "@/components/ResetPass.vue";
import {showMessageError} from "@/utils/dialog";
const router = useRouter();
const title = ref('Geek-AI');
@ -76,7 +76,7 @@ httpGet("/api/config/get?key=system").then(res => {
logo.value = res.data.logo
title.value = res.data.title
}).catch(e => {
ElMessage.error("获取系统配置失败:" + e.message)
showMessageError("获取系统配置失败:" + e.message)
})
@ -97,10 +97,10 @@ const handleKeyup = (e) => {
const login = function () {
if (username.value.trim() === '') {
return ElMessage.error("请输入用户民")
return showMessageError("请输入用户民")
}
if (password.value.trim() === '') {
return ElMessage.error('请输入密码');
return showMessageError('请输入密码');
}
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
@ -112,11 +112,10 @@ const login = function () {
}
}).catch((e) => {
ElMessage.error('登录失败,' + e.message)
showMessageError('登录失败,' + e.message)
})
}
</script>
<style lang="stylus" scoped>

View File

@ -180,6 +180,7 @@ import SendMsg from "@/components/SendMsg.vue";
import {arrayContains} from "@/utils/libs";
import {setUserToken} from "@/store/session";
import {validateEmail, validateMobile} from "@/utils/validate";
import {showMessageError, showMessageOK} from "@/utils/dialog";
const router = useRouter();
const title = ref('Geek-AI 用户注册');
@ -227,37 +228,37 @@ httpGet("/api/config/get?key=system").then(res => {
//
const submitRegister = () => {
if (data.value.username === '') {
return ElMessage.error('请输入用户名');
return showMessageError('请输入用户名');
}
if (activeName.value === 'mobile' && !validateMobile(data.value.username)) {
return ElMessage.error('请输入合法的手机号');
return showMessageError('请输入合法的手机号');
}
if (activeName.value === 'email' && !validateEmail(data.value.username)) {
return ElMessage.error('请输入合法的邮箱地址');
return showMessageError('请输入合法的邮箱地址');
}
if (data.value.password.length < 8) {
return ElMessage.error('密码的长度为8-16个字符');
return showMessageError('密码的长度为8-16个字符');
}
if (data.value.repass !== data.value.password) {
return ElMessage.error('两次输入密码不一致');
return showMessageError('两次输入密码不一致');
}
if ((activeName.value === 'mobile' || activeName.value === 'email') && data.value.code === '') {
return ElMessage.error('请输入验证码');
return showMessageError('请输入验证码');
}
data.value.reg_way = activeName.value
httpPost('/api/user/register', data.value).then((res) => {
setUserToken(res.data)
ElMessage.success({
showMessageOK({
"message": "注册成功,即将跳转到对话主界面...",
onClose: () => router.push("/chat"),
duration: 1000
})
}).catch((e) => {
ElMessage.error('注册失败,' + e.message)
showMessageError('注册失败,' + e.message)
})
}

View File

@ -134,6 +134,7 @@ import ChatReply from "@/components/mobile/ChatReply.vue";
import {getSessionId, getUserToken} from "@/store/session";
import {checkSession} from "@/action/session";
import Clipboard from "clipboard";
import {showLoginDialog} from "@/utils/dialog";
const winHeight = ref(0)
const navBarRef = ref(null)
@ -178,6 +179,7 @@ httpGet('/api/model/list').then(res => {
}
for (let i = 0; i < models.value.length; i++) {
models.value[i].text = models.value[i].name
models.value[i].mValue = models.value[i].value
models.value[i].value = models.value[i].id
}
modelValue.value = getModelValue(modelId.value)
@ -223,7 +225,10 @@ onMounted(() => {
})
onUnmounted(() => {
socket.value = null
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
})
const newChat = (item) => {
@ -275,35 +280,38 @@ md.use(mathjaxPlugin)
const onLoad = () => {
if (chatId.value) {
httpGet('/api/chat/history?chat_id=' + chatId.value).then(res => {
//
finished.value = true;
const data = res.data
if (data && data.length > 0) {
for (let i = 0; i < data.length; i++) {
if (data[i].type === "prompt") {
checkSession().then(() => {
httpGet('/api/chat/history?chat_id=' + chatId.value).then(res => {
//
finished.value = true;
const data = res.data
if (data && data.length > 0) {
for (let i = 0; i < data.length; i++) {
if (data[i].type === "prompt") {
chatData.value.push(data[i]);
continue;
}
data[i].orgContent = data[i].content;
data[i].content = md.render(processContent(data[i].content))
chatData.value.push(data[i]);
continue;
}
data[i].orgContent = data[i].content;
data[i].content = md.render(processContent(data[i].content))
chatData.value.push(data[i]);
}
nextTick(() => {
hl.configure({ignoreUnescapedHTML: true})
const blocks = document.querySelector("#message-list-box").querySelectorAll('pre code');
blocks.forEach((block) => {
hl.highlightElement(block)
})
nextTick(() => {
hl.configure({ignoreUnescapedHTML: true})
const blocks = document.querySelector("#message-list-box").querySelectorAll('pre code');
blocks.forEach((block) => {
hl.highlightElement(block)
scrollListBox()
})
scrollListBox()
})
}
connect(chatId.value, roleId.value, modelId.value);
}
connect(chatId.value, roleId.value, modelId.value);
}).catch(() => {
error.value = true
})
}).catch(() => {
error.value = true
})
}
};
@ -443,8 +451,7 @@ const connect = function (chat_id, role_id, model_id) {
checkSession().then(() => {
connect(chat_id, role_id, model_id)
}).catch(() => {
loading.value = true
setTimeout(() => connect(chat_id, role_id, model_id), 3000)
showLoginDialog(router)
});
});
@ -549,7 +556,7 @@ const getRoleById = function (rid) {
const getModelValue = (model_id) => {
for (let i = 0; i < models.value.length; i++) {
if (models.value[i].id === model_id) {
return models.value[i].value
return models.value[i].mValue
}
}
return ""

View File

@ -57,16 +57,6 @@ bus.on('changeTheme', (value) => {
background #1c1c1e
}
.van-toast--fail {
background #fef0f0
color #f56c6c
}
.van-toast--success {
background #D6FBCC
color #07C160
}
.van-nav-bar {
position fixed
width 100%

View File

@ -95,6 +95,7 @@ onMounted(() => {
checkSession().then((user) => {
isLogin.value = true
roles.value = user.chat_roles
}).catch(() => {
})
fetchApps()
})

View File

@ -276,14 +276,7 @@
<script setup>
import {nextTick, onMounted, onUnmounted, ref} from "vue";
import {
showConfirmDialog,
showFailToast,
showNotify,
showToast,
showImagePreview,
showSuccessToast
} from "vant";
import {showConfirmDialog, showFailToast, showImagePreview, showNotify, showSuccessToast, showToast} from "vant";
import {httpGet, httpPost} from "@/utils/http";
import Compressor from "compressorjs";
import {getSessionId} from "@/store/session";
@ -364,8 +357,11 @@ onMounted(() => {
})
onUnmounted(() => {
socket.value = null
clipboard.value.destroy()
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
})
const mjPower = ref(1)

View File

@ -221,7 +221,8 @@ import {checkSession} from "@/action/session";
import {useRouter} from "vue-router";
import {getSessionId} from "@/store/session";
import {
showConfirmDialog, showDialog,
showConfirmDialog,
showDialog,
showFailToast,
showImagePreview,
showNotify,
@ -357,7 +358,10 @@ onMounted(() => {
onUnmounted(() => {
clipboard.value.destroy()
socket.value = null
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
})