Browse Source

change localchat

master
godo 10 months ago
parent
commit
d69be8c91b
  1. 17
      CHANGELOG.md
  2. 1
      Dockerfile
  3. 4
      frontend/src/components/localchat/Chat.vue
  4. 21
      frontend/src/components/localchat/ChatDomain.vue
  5. 74
      frontend/src/components/localchat/ChatNav.vue
  6. 194
      frontend/src/components/setting/SetSystem.vue
  7. 310
      frontend/src/stores/localchat.ts
  8. 12
      frontend/src/system/config.ts
  9. 13
      frontend/src/system/core/FileOs.ts
  10. 1
      godo/build.sh
  11. 3
      godo/cmd/main.go
  12. 12
      godo/files/fs.go
  13. 37
      godo/libs/config.go
  14. 25
      godo/libs/dir.go
  15. 216
      godo/libs/info.go
  16. 61
      godo/libs/key.go
  17. 21
      godo/localchat/sse.go
  18. 65
      godo/sys/setting.go
  19. 2
      main.go

17
CHANGELOG.md

@ -1,4 +1,10 @@
## 变更记录
内网聊天增加手工添加ip,跨网段通信在ping通的前提下如果发现不了对方可手工添加对方ip
修复思维导图保存的文件每次打开主题又会变成默认主题
新增webdav客户端
应用商店新增可随机启动
- 2024-08-07
1. 新增web端安装
- 2024-08-06
@ -8,4 +14,13 @@
- 2024-08-02
1. 修复桌面更换背景文字颜色问题
2. 修复安装插件没有配置binPath的问题
3. 新增linux环境下获取安装命令的方法
3. 新增linux环境下获取安装命令的方法
bug梳理:
1. 思维导图保存的文件每次打开主题又会变成默认主题
2. 公网版部署udp转发和文件上传存储等
3. 内网聊天功能,如果是装了虚拟机的电脑,会出现多个虚拟网卡,内网可以看到用户但是IP不对着,发消息收不到的
4. 建议开启webDav
5. web 的甘特图保存不了
6. 场景适用性:系统默认连接gitee远程软件安装包,缺少默认读取本地安装包路径的json配置文件的打包设置选项(比如在安装时自动读取安装U盘当前目录中/或已直接打包进安装包中的mysql/nginx/php/python本地离线安装包、以及企业自用OA WebApp的本地离线安装包,无需联网,直接同步离线安装)
7. 功能完整性:安装的服务型应用(如mysql/nginx/php/python)没有跟随软件环境启动后自动启动的设置选项,依赖于服务型应用的AI Web UI、php /python应用没有创建桌面快捷方式/菜单快捷方式的入口(即:AI Web UI、php /python应用无法安装和自动开箱即用)。

1
Dockerfile

@ -9,6 +9,7 @@ ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
GODOTOPTYPE=docker
# 设置后续指令的工作目录
WORKDIR /build

4
frontend/src/components/localchat/Chat.vue

@ -1,12 +1,12 @@
<template>
<el-row justify="space-between">
<el-col :span="2">
<el-col :span="1">
<chat-nav />
</el-col>
<el-col :span="6">
<chat-domain />
</el-col>
<el-col :span="16">
<el-col :span="17">
<chat-content />
</el-col>
</el-row>

21
frontend/src/components/localchat/ChatDomain.vue

@ -38,11 +38,13 @@
</div>
<div v-else class="user-list-area">
<el-row
justify="start">
<el-row justify="space-between">
<el-icon :size="18" @click="store.refreshUserList">
<RefreshRight />
</el-icon>
<el-icon :size="18" @click="store.showAddUser = true">
<CirclePlusFilled />
</el-icon>
</el-row>
<el-row
class="user-list"
@ -71,6 +73,20 @@
</div>
</el-scrollbar>
</div>
<el-dialog v-model="store.showAddUser" title="添加用户" width="500">
<el-form>
<el-form-item :label-width="0">
<el-input v-model="userIp" autocomplete="off" placeholder="输入用户IP 例如:192.168.1.16"/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="store.addUser(userIp)">
添加
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
@ -78,6 +94,7 @@ import { useLocalChatStore } from "@/stores/localchat";
import { formatChatTime } from "@/util/common";
const store = useLocalChatStore();
const userIp = ref('')
</script>
<style scoped lang="scss">
.win11-msg-container {

74
frontend/src/components/localchat/ChatNav.vue

@ -1,19 +1,15 @@
<template>
<el-space direction="vertical" :size="20" class="win11-chat-nav">
<div class="nav-item" v-for="item in store.navList" :key="item.index">
<el-button
:icon="item.icon"
:class="store.navId === item.index ? 'active' : ''"
dark
circle
@click="store.handleSelect(item.index)"
/>
<div :class="store.navId === item.index ? 'nav-item active' : 'nav-item'" v-for="item in store.navList" :key="item.index">
<el-icon size="18" @click="store.handleSelect(item.index)">
<component :is="item.icon" />
</el-icon>
</div>
</el-space>
</template>
<script setup lang="ts">
import { useLocalChatStore } from "@/stores/localchat";
import { useLocalChatStore } from '@/stores/localchat';
const store = useLocalChatStore();
</script>
<style lang="scss" scoped>
@ -21,7 +17,7 @@ const store = useLocalChatStore();
height: 100vh;
background-color: #f8f8f8; /* 使用更亮的淡灰色,更接近Win11的背景色 */
border-right: 1px solid rgba(230, 230, 230, 0.5); /* 更浅的边框颜色 */
padding: 16px;
padding: 8px;
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
overflow-y: auto;
display: flex;
@ -38,24 +34,20 @@ const store = useLocalChatStore();
align-items: center;
justify-content: center;
width: 100%;
padding: 3px;
padding: 2px;
border-radius: 50%; /* 圆角 */
background-color: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 渐变阴影 */
background-color: #f8f7f7; /* 更柔和的背景色 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* 更轻的阴影 */
transition: all 0.2s ease-in-out;
&:hover {
background-color: #f0f0f0; /* 鼠标悬停时的轻微颜色变化 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
/* 修复 active 类的优先级问题 */
&.active {
/* 加强背景色对比,使用Win11的强调色或品牌色 */
background-color: #4579a1; /* 示例颜色,可根据设计调整 */
background-color: #0078d4; /* 深蓝色,Win11 的强调色 */
color: white; /* 文字颜色反转,确保可读性 */
/* 增加外边框以进一步区分 */
border: 2px solid #005a9c; /* 较深的强调色作为边框 */
border: 1px solid #005a9c; /* 较深的强调色作为边框 */
border-radius: 50%;
/* 内发光效果,让按钮看起来更‘活跃’ */
box-shadow: 0 5px 8px rgba(0, 120, 212, 0.5) inset;
@ -66,22 +58,12 @@ const store = useLocalChatStore();
/* 确保文字在按下时不会因按钮尺寸变化而偏移 */
transition-property: background-color, box-shadow, transform, color;
/* 如果按钮包含图标,可以考虑为图标也添加强调效果,例如改变颜色 */
.el-icon {
color: inherit; /* 或指定特定强调色 */
}
/* 为了平滑的过渡,确保所有相关属性都被包含在transition中 */
}
/* 考虑到按钮是圆形且使用了 `circle` 属性,确保图标和背景颜色调整得当 */
&.active .el-button {
background-color: transparent !important; /* 确保背景色不影响图标颜色 */
}
/* 图标颜色调整,确保在active状态下足够突出 */
&.active .el-icon {
color: #ffffff; /* 确保图标颜色与背景对比鲜明 */
/* 修复 hover 效果 */
&:hover {
background-color: #eaeaea; /* 鼠标悬停时的轻微颜色变化 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* 非活动状态的悬停效果,保持与.active状态的区分 */
@ -89,5 +71,29 @@ const store = useLocalChatStore();
/* 调整以与.active状态区分,例如使用较浅的颜色 */
background-color: #eaeaea;
}
/* 修复 el-icon 默认样式的覆盖问题 */
.el-icon {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 0;
border-radius: inherit;
background-color: inherit;
box-shadow: inherit;
transition: inherit;
cursor: pointer;
/* 修复 active 和 hover 效果 */
&[class*="active"],
&:hover {
background-color: inherit;
box-shadow: inherit;
transform: inherit;
transition: inherit;
}
}
}
</style>

194
frontend/src/components/setting/SetSystem.vue

@ -2,12 +2,8 @@
<div class="container">
<div class="nav">
<ul>
<li
v-for="(item, index) in items"
:key="index"
@click="selectItem(index)"
:class="{ active: index === activeIndex }"
>
<li v-for="(item, index) in items" :key="index" @click="selectItem(index)"
:class="{ active: index === activeIndex }">
{{ item }}
</li>
</ul>
@ -17,30 +13,17 @@
<div class="setting-item" style="margin-top: 60px">
<label>存储方式</label>
<el-select v-model="config.storeType">
<el-option
v-for="(item, key) in storeList"
:key="key"
:label="item.title"
:value="item.value"
/>
<el-option v-for="(item, key) in storeList" :key="key" :label="item.title" :value="item.value" />
</el-select>
</div>
<div class="setting-item" v-if="config.storeType === 'local'">
<label>存储地址</label>
<el-input v-model="config.storePath" @click="selectFile()" />
<el-input v-model="config.storePath" @click="selectFile()" placeholder="可为空,为空则取系统默认存储地址"/>
</div>
<template v-if="config.storeType === 'net'">
<div class="setting-item">
<label>服务器地址</label>
<el-input v-model="config.storenet.url" />
</div>
<div class="setting-item">
<label>用户名</label>
<el-input v-model="config.storenet.username" />
</div>
<div class="setting-item">
<label>密码</label>
<el-input v-model="config.storenet.password" type="password" />
<el-input v-model="config.storenet.url" placeholder="http://192.168.1.16 不要加斜杠"/>
</div>
</template>
@ -51,50 +34,8 @@
</el-button>
</div>
</div>
<div v-if="1 === activeIndex">
<div class="setting-item" style="margin-top: 60px">
<label>用户角色</label>
<el-select v-model="config.userType">
<el-option
v-for="(item, key) in userTypeList"
:key="key"
:label="item.title"
:value="item.value"
/>
</el-select>
</div>
<template v-if="config.userType === 'compony'">
<div class="setting-item">
<label>管理用户名</label>
<el-input v-model="config.userInfo.username" />
</div>
<div class="setting-item">
<label>管理密码</label>
<el-input v-model="config.userInfo.password" type="password" />
</div>
</template>
<template v-if="config.userType === 'member'">
<div class="setting-item">
<label>服务器地址</label>
<el-input v-model="config.userInfo.serverUrl" />
</div>
<div class="setting-item">
<label>登陆用户名</label>
<el-input v-model="config.userInfo.username" />
</div>
<div class="setting-item">
<label>登陆密码</label>
<el-input v-model="config.userInfo.password" type="password" />
</div>
</template>
<div class="setting-item">
<label></label>
<el-button @click="submitUserInfo" type="primary">
{{ t("confirm") }}
</el-button>
</div>
</div>
<div v-if="2 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">备份</h1>
</div>
@ -141,22 +82,8 @@ const storeList = [
value: "net",
},
];
const userTypeList = [
{
title: "个人用户",
value: "person",
},
{
title: "企业用户",
value: "member",
},
{
title: "企业管理员",
value: "compony",
},
];
const items = ["个人存储", "用户角色", "备份还原"];
const items = ["个人存储","备份还原"];
const activeIndex = ref(0);
@ -172,20 +99,40 @@ function selectFile() {
function submitOsInfo() {
const saveData = toRaw(config.value);
const postData: any = {
name: "osInfo",
//name: "osPath",
type: saveData.storeType,
};
if (saveData.storeType === "local") {
if (saveData.storePath === "") {
// Dialog.showMessageBox({
// message: "",
// type: "error",
// });
setSystemConfig(saveData);
RestartApp();
return;
}
postData.name = "osPath";
postData.value = saveData.storePath;
const postUrl = config.value.apiUrl + "/system/setting";
fetch(postUrl, {
method: "POST",
body: JSON.stringify([postData]),
})
.then((res) => res.json())
.then((res) => {
if (res.code === 0) {
setSystemConfig(saveData);
Dialog.showMessageBox({
message: t("save.success"),
title: t("language"),
type: "info",
}).then(() => {
RestartApp();
});
} else {
Dialog.showMessageBox({
message: res.message,
type: "error",
});
}
});
}
if (saveData.storeType === "net") {
if (saveData.storenet.url === "") {
@ -195,79 +142,26 @@ function submitOsInfo() {
});
return;
}
if (saveData.storenet.username === "") {
Dialog.showMessageBox({
message: "用户名不能为空",
type: "error",
});
return;
}
if (saveData.storenet.password === "") {
const urlRegex = /^(https?:\/\/)[^\/]+$/;
if(!urlRegex.test(saveData.storenet.url)){
Dialog.showMessageBox({
message: "密码不能为空",
message: "服务器地址格式错误",
type: "error",
});
return;
}
}
const postUrl = config.value.apiUrl + "/system/setting";
fetch(postUrl, {
method: "POST",
body: JSON.stringify(postData),
})
.then((res) => res.json())
.then((res) => {
//console.log(res);
if (res.code === 0) {
setSystemConfig(saveData);
Dialog.showMessageBox({
message: t("save.success"),
title: t("language"),
type: "info",
}).then(() => {
//location.reload();
RestartApp();
});
} else {
Dialog.showMessageBox({
message: res.message,
type: "error",
});
}
});
}
function submitUserInfo() {
const saveData = toRaw(config.value);
if (saveData.userType === "member" && saveData.userInfo.serverUrl == "") {
setSystemConfig(saveData);
Dialog.showMessageBox({
message: "服务器地址不能为空",
type: "error",
message: t("save.success"),
title: t("language"),
type: "info",
}).then(() => {
RestartApp();
});
return;
}
if (saveData.userType !== "person") {
if (!saveData.userInfo.username && saveData.userInfo.username == "") {
Dialog.showMessageBox({
message: "用户名不能为空",
type: "error",
});
return;
}
if (!saveData.userInfo.password && saveData.userInfo.password == "") {
Dialog.showMessageBox({
message: "密码不能为空",
type: "error",
});
return;
}
}
setSystemConfig(saveData);
Dialog.showMessageBox({
message: t("save.success"),
title: t("language"),
type: "info",
});
}
async function exportBackup() {
const { setProgress } = Dialog.showProcessDialog({
message: `正在打包`,
@ -373,9 +267,11 @@ async function importBackup(path = "") {
</script>
<style scoped>
@import "./setStyle.css";
.ctrl {
width: 100px;
}
.setting-item {
display: flex;
align-items: center;

310
frontend/src/stores/localchat.ts

@ -2,60 +2,62 @@ import { defineStore } from 'pinia'
import emojiList from "@/assets/emoji.json"
import { ref, toRaw, inject } from "vue";
import { db } from './db'
import { System,dirname } from "@/system";
import { System, dirname } from "@/system";
import { getSystemConfig } from "@/system/config";
import { isBase64, base64ToBuffer } from "@/util/file";
import { notifyError, notifySuccess } from "@/util/msg";
export const useLocalChatStore = defineStore('localChatStore', () => {
const config = getSystemConfig();
const sys = inject<System>("system");
const userList:any = ref([])
const msgList:any = ref([])
const contentList:any = ref([])
const hostInfo:any = ref({})
const userList: any = ref([])
const msgList: any = ref([])
const contentList: any = ref([])
const OutUserList: any = ref([])
const hostInfo: any = ref({})
const showChooseFile = ref(false)
const currentPage = ref(1)
const pageSize = ref(50)
const navList = ref([
{ index: 1, lable: "消息列表", icon: "ChatDotRound", type:"success" },
{ index: 1, lable: "消息列表", icon: "ChatDotRound", type: "success" },
{ index: 2, lable: "用户列表", icon: "UserFilled", type: "info" },
])
const navId = ref(1)
const sendInfo = ref("")
const chatTargetId = ref(0)
const chatTargetIp = ref("")
const showAddUser = ref(false)
const handleSelect = (key: number) => {
navId.value = key;
};
const setChatId = async(ip : string) => {
const setChatId = async (ip: string) => {
//console.log(ip)
//chatTargetId.value = id
chatTargetIp.value = ip
const data = await db.get("chatuser", {ip : ip})
if(!data)return;
const data = await db.get("chatuser", { ip: ip })
if (!data) return;
chatTargetId.value = data.id
clearContentList(data.id)
currentPage.value = 1
await getMsgList()
}
const initContentList = async () => {
const list:any = {}
const list: any = {}
const msgAll = await db.getAll('chatmsg')
msgAll.forEach((d : any) => {
if(!d.isMe){
if (!list[d.targetIp]){
msgAll.forEach((d: any) => {
if (!d.isMe) {
if (!list[d.targetIp]) {
list[d.targetIp] = []
}
list[d.targetIp].push(d)
}
})
const res = []
for(const p in list){
for (const p in list) {
const chatArr = list[p]
let readNum = 0
chatArr.forEach((d:any) => {
if(!d.isRead){
chatArr.forEach((d: any) => {
if (!d.isRead) {
readNum++
}
})
@ -65,23 +67,23 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
}
contentList.value = res.sort((a, b) => b.createdAt - a.createdAt);
}
const clearContentList = (targetId:number) => {
contentList.value.forEach((d : any) => {
if(d.targetId === targetId) {
const clearContentList = (targetId: number) => {
contentList.value.forEach((d: any) => {
if (d.targetId === targetId) {
d.readNum = 0
}
})
}
const clearMsg = async () => {
if (chatTargetIp.value === '') return
await db.deleteByField('chatmsg','targetIp', chatTargetIp.value)
await db.deleteByField('chatmsg', 'targetIp', chatTargetIp.value)
msgList.value = []
}
const updateContentList = async (msg:any) => {
if(msg.isMe)return;
const has = contentList.value.find((d:any) => d.targetIp === msg.targetIp)
if(has){
const updateContentList = async (msg: any) => {
if (msg.isMe) return;
const has = contentList.value.find((d: any) => d.targetIp === msg.targetIp)
if (has) {
contentList.value.forEach((d: any, index: number) => {
if (d.targetIp === msg.targetIp) {
if (!msg.isRead) {
@ -94,10 +96,10 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
}
});
//console.log(contentList.value)
}else{
if(msg.isRead){
} else {
if (msg.isRead) {
msg.readNum = 0
}else{
} else {
msg.readNum = 1
}
contentList.value.unshift(msg)
@ -105,14 +107,14 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
}
contentList.value = contentList.value.sort((a: any, b: any) => b.createdAt - a.createdAt)
}
const init = async() => {
const init = async () => {
await getUserList()
await initUserList()
await initContentList()
}
const initUserList = async() => {
if(userList.value.length > 0) {
const updates : any = []
const initUserList = async () => {
if (userList.value.length > 0) {
const updates: any = []
userList.value.forEach((d: any) => {
if (d.isOnline) {
updates.push({
@ -131,9 +133,7 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
userList.value = []
}
const getMsgList = async () => {
if (chatTargetId.value < 1)return;
//msgList.value = await db.getByField('chatmsg', 'targetId', chatTargetId.value)
//msgList.value = await db.pageSearch('chatmsg', currentPage.value, pageSize.value, { targetId: chatTargetId.value })
if (chatTargetId.value < 1) return;
const offset = (currentPage.value - 1) * pageSize.value
const list = await db.table('chatmsg')
.where({ targetIp: chatTargetIp.value })
@ -141,10 +141,10 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
.offset(offset)
.limit(pageSize.value)
.toArray();
list.sort((a:any,b:any) => a.id > b.id)
list.sort((a: any, b: any) => a.id > b.id)
msgList.value = list;
}
const moreMsgList = async() => {
const moreMsgList = async () => {
if (chatTargetId.value < 1) return;
//const list = await db.pageSearch('chatmsg', currentPage.value + 1, pageSize.value, { targetId: chatTargetId.value })
const offset = currentPage.value * pageSize.value
@ -154,73 +154,77 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
.offset(offset)
.limit(pageSize.value)
.toArray();
if(list && list.length > 0) {
if (list && list.length > 0) {
list.sort((a: any, b: any) => a.id > b.id)
currentPage.value = currentPage.value + 1
msgList.value = [...list, ...msgList.value];
}
}
const getUserList = async () => {
const list = await db.getAll('chatuser')
list.sort((a: any, b: any) => a.updatedAt > b.updatedAt)
userList.value = list
const listAll = await db.getAll('chatuser')
const list = [...listAll, ...OutUserList.value]
let uniqueIpMap = new Map<string, any>();
// 遍历 list 并添加 IP 地址到 Map 中
list.forEach((item: any) => {
uniqueIpMap.set(item.ip, item);
});
// 将 Map 转换回数组
const uniqueIpList: any = Array.from(uniqueIpMap.values());
uniqueIpList.sort((a: any, b: any) => a.updatedAt > b.updatedAt)
userList.value = uniqueIpList
}
const setUserList = async (data:any) => {
const setUserList = async (data: any) => {
//console.log(data)
if(data.length < 1){
if (data.length < 1) {
return
}
hostInfo.value = data[0]
data.shift()
const ips:any = []
userList.value.forEach((d : any) => {
ips.push(d.ip)
});
const has:any = []
const nothas:any = []
data.forEach((d:any) => {
if(ips.includes(d.ip)){
has.push(d.ip)
}else{
nothas.push(d)
const existingIps = new Set(userList.value.map((d : any) => d.ip));
const updates: any[] = [];
const newEntries: any[] = [];
data.forEach((d : any) => {
if (existingIps.has(d.ip)) {
updates.push({
key: d.id,
changes: {
isOnline: true,
updatedAt: Date.now()
}
});
} else {
newEntries.push({
ip: d.ip,
isOnline: true,
username: d.hostname,
createdAt: Date.now(),
updatedAt: Date.now()
});
}
})
if(has.length > 0) {
const updates:any = []
userList.value.forEach((d: any) => {
if(has.includes(d.ip)){
updates.push({
key : d.id,
changes : {
isOnline : true,
updatedAt:Date.now()
}
})
}
});
await db.table('chatuser').bulkUpdate(updates)
});
if (updates.length > 0) {
await db.table('chatuser').bulkUpdate(updates);
}
if(nothas.length > 0) {
nothas.forEach((d:any) => {
d.isOnline = true
d.username = data.hostname
d.createdAt = Date.now()
d.updatedAt = Date.now()
})
await db.table('chatuser').bulkAdd(nothas)
if (newEntries.length > 0) {
await db.table('chatuser').bulkAdd(newEntries);
}
await getUserList()
}
const addFile = async (data:any) => {
const targetUser:any = await getTargetUser(data)
const files:any = []
data.fileList.forEach((d:any) => {
const addFile = async (data: any) => {
const targetUser: any = await getTargetUser(data)
const files: any = []
data.fileList.forEach((d: any) => {
d.save_path = d.save_path.replace(/\\/g, "/");
files.push({
name : d.name,
path : d.save_path,
ext : d.save_path.split('.').pop(),
name: d.name,
path: d.save_path,
ext: d.save_path.split('.').pop(),
content: d.content
})
})
@ -240,23 +244,23 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
saveMsg.isRead = true
msgList.value.push(saveMsg)
}
console.log(saveMsg)
//console.log(saveMsg)
await db.addOne('chatmsg', saveMsg)
//await getMsgList()
await updateContentList(saveMsg)
if (config.storeType === 'browser') {
await storeFile(files)
}
handleSelect(1)
}
const storeFile = async(fileList : any) => {
const storeFile = async (fileList: any) => {
if (fileList.length < 1) return;
console.log(fileList)
for (let i = 0; i < fileList.length; i++) {
let content = fileList[i].content
if(typeof content === 'string') {
if(isBase64(content)){
if (typeof content === 'string') {
if (isBase64(content)) {
content = base64ToBuffer(content);
}
const path = dirname(fileList[i].path)
@ -266,26 +270,26 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
}
}
}
const getTargetUser = async (data:any) => {
let targetUser:any = userList.value.find((d: any) => d.ip === data.senderInfo.ip)
if (!targetUser){
const getTargetUser = async (data: any) => {
let targetUser: any = userList.value.find((d: any) => d.ip === data.senderInfo.ip)
if (!targetUser) {
targetUser = {
isOnline : true,
ip:data.senderInfo.ip,
isOnline: true,
ip: data.senderInfo.ip,
hostname: data.senderInfo.hostname,
username : data.senderInfo.hostname,
createdAt : Date.now(),
updatedAt : Date.now()
username: data.senderInfo.hostname,
createdAt: Date.now(),
updatedAt: Date.now()
}
targetUser.id = await db.addOne("chatuser",targetUser)
targetUser.id = await db.addOne("chatuser", targetUser)
userList.value.unshift(targetUser)
}
return targetUser
}
const addText = async (data:any) => {
const targetUser:any = await getTargetUser(data)
const addText = async (data: any) => {
const targetUser: any = await getTargetUser(data)
const saveMsg:any = {
const saveMsg: any = {
type: 'text',
targetId: targetUser.id,
targetIp: targetUser.ip,
@ -296,7 +300,7 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
isRead: false,
status: 'reciped'
}
if (targetUser.id === chatTargetId.value){
if (targetUser.id === chatTargetId.value) {
saveMsg.readAt = Date.now()
saveMsg.isRead = true
msgList.value.push(saveMsg)
@ -304,24 +308,24 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
//console.log(saveMsg)
await db.addOne('chatmsg', saveMsg)
//await getMsgList()
await updateContentList(saveMsg)
handleSelect(1)
}
const sendMsg = async () => {
if(chatTargetId.value < 1) {
if (chatTargetId.value < 1) {
return
}
const saveMsg:any = {
type : 'text',
targetId : chatTargetId.value,
const saveMsg: any = {
type: 'text',
targetId: chatTargetId.value,
targetIp: chatTargetIp.value,
content: sendInfo.value.trim(),
senderInfo: toRaw(hostInfo.value),
createdAt:Date.now(),
isMe:true,
isRead:false,
status:'sending'
createdAt: Date.now(),
isMe: true,
isRead: false,
status: 'sending'
}
//console.log(saveMsg)
const msgId = await db.addOne('chatmsg', saveMsg)
@ -329,7 +333,7 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
msgList.value.push(saveMsg)
const targetUser = userList.value.find((d: any) => d.id === chatTargetId.value)
//console.log(targetUser)
if(targetUser.isOnline) {
if (targetUser.isOnline) {
const postUrl = `http://${targetUser.ip}:56780/localchat/message`
const completion = await fetch(postUrl, {
method: "POST",
@ -337,7 +341,7 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
})
if (!completion.ok) {
console.log(completion)
}else{
} else {
saveMsg.isRead = true
saveMsg.status = 'sended'
saveMsg.readAt = Date.now()
@ -347,10 +351,10 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
}
sendInfo.value = ""
await updateContentList(saveMsg)
}
//上传文件资源
async function uploadFile(paths:any) {
async function uploadFile(paths: any) {
if (chatTargetId.value < 1) {
return
}
@ -365,12 +369,12 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
}
//console.log(paths)
const formData = new FormData();
const errstr:any = []
const files:any = []
const errstr: any = []
const files: any = []
for (let i = 0; i < paths.length; i++) {
const content = await sys?.fs.readFile(paths[i]);
let blobContent;
if(!content || content == ''){
if (!content || content == '') {
errstr.push(paths[i] + " is empty")
continue
}
@ -393,13 +397,13 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
files.push({
name: fileName,
path: paths[i],
ext : fileName.split(".").pop(),
ext: fileName.split(".").pop(),
})
//files.push(blobContent);
formData.append(`files`, blobContent, fileName);
}
if(errstr.length > 0) {
errstr.forEach((d:any) => {
if (errstr.length > 0) {
errstr.forEach((d: any) => {
notifyError(d);
})
return
@ -429,17 +433,52 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
isRead: true,
status: 'reciped'
}
console.log(saveMsg)
//console.log(saveMsg)
await db.addOne('chatmsg', saveMsg)
msgList.value.push(saveMsg)
notifySuccess("upload success!");
}
return {
userList,
navList,
function isValidIP(ip: string): boolean {
// 正则表达式用于匹配 IPv4 地址
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
// 正则表达式用于匹配 IPv6 地址
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4})$/;
// 验证 IP 地址
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
}
async function addUser(ip: string) {
if (!isValidIP(ip)) {
notifyError("请输入正确的IP地址");
return
}
const postUrl = `http://${ip}:56780/localchat/check`
const completion = await fetch(postUrl)
if (!completion.ok) {
notifyError('用户不在线')
} else {
const res = await completion.json()
const data = res.data
data.createdAt = Date.now()
data.updatedAt = Date.now()
data.isOnline = true
data.username = data.hostname
if (!OutUserList.value.some((item: any) => item.ip === data.ip)) {
OutUserList.value.push(data);
await getUserList()
}
}
}
return {
userList,
navList,
sendInfo,
navId,
navId,
chatTargetId,
chatTargetIp,
msgList,
@ -448,6 +487,8 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
emojiList,
showChooseFile,
pageSize,
showAddUser,
OutUserList,
init,
setUserList,
getUserList,
@ -459,6 +500,19 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
uploadFile,
moreMsgList,
refreshUserList,
clearMsg
}
clearMsg,
addUser
}
}, {
persist: {
enabled: true,
strategies: [
{
storage: localStorage,
paths: [
"OutUserList"
]
}, // name 字段用localstorage存储
],
}
})

12
frontend/src/system/config.ts

@ -1,3 +1,4 @@
import { get } from "http";
import { generateRandomString } from "../util/common.ts"
export const configStoreType = localStorage.getItem('GodoOS-storeType') || 'browser';
/**
@ -129,7 +130,16 @@ export const getSystemConfig = (ifset = false) => {
export function getApiUrl() {
return getSystemKey('apiUrl')
}
export function getFileUrl() {
const config = getSystemConfig();
if(config.storeType == 'local'){
return config.apiUrl
}
if(config.storeType == 'net'){
return config.storenet.url
}
return config.apiUrl
}
export function isWindowsOS() {
return /win64|wow64|win32|win16|wow32/i.test(navigator.userAgent);

13
frontend/src/system/core/FileOs.ts

@ -1,21 +1,12 @@
// import { useSystemStore } from "./system.ts"
// const systemStore = useSystemStore();
// const API_BASE_URL = systemStore.getFileUrl()
import { getSystemKey } from "../config.ts";
const API_BASE_URL = getSystemKey('apiUrl') + "/file"
import { getFileUrl } from "../config.ts";
const API_BASE_URL = getFileUrl() + "/file"
import { OsFileMode } from '../core/FileMode';
export async function handleReadDir(path: string): Promise<any> {
// if(window.go){
// return await window.go.app.App.ReadDir(path)
// }else{
// }
const res = await fetch(`${API_BASE_URL}/read?path=${encodeURIComponent(path)}`);
if (!res.ok) {
return false;
}
return await res.json();
}
export async function handleStat(path: string): Promise<any> {

1
godo/build.sh

@ -27,6 +27,7 @@ for PLATFORM in "${PLATFORMS[@]}"; do
# 设置GOOS和GOARCH环境变量
export GOOS=$OS
export GOARCH=$ARCH
export GODOTOPTYPE="web"
# 执行编译命令,并处理可能的错误
go build -o "$OUTPUT_FILE" ./main.go || { echo "编译 $OS/$ARCH 失败,请检查错误并尝试解决。"; continue; }

3
godo/cmd/main.go

@ -57,7 +57,7 @@ func OsStart() {
router.HandleFunc("/system/updateInfo", sys.GetUpdateUrlHandler).Methods(http.MethodGet)
router.HandleFunc("/system/update", sys.UpdateAppHandler).Methods(http.MethodGet)
router.HandleFunc("/system/setting", sys.HandleSetConfig).Methods(http.MethodPost)
router.HandleFunc("/system/setting", sys.ConfigHandler).Methods(http.MethodPost)
router.HandleFunc("/file/info", files.HandleSystemInfo).Methods(http.MethodGet)
router.HandleFunc("/file/read", files.HandleReadDir).Methods(http.MethodGet)
@ -79,6 +79,7 @@ func OsStart() {
router.HandleFunc("/localchat/sse", localchat.SseHandler).Methods(http.MethodGet)
router.HandleFunc("/localchat/message", localchat.HandleMessage).Methods(http.MethodPost)
router.HandleFunc("/localchat/upload", localchat.MultiUploadHandler).Methods(http.MethodPost)
router.HandleFunc("/localchat/check", localchat.CheckUserHanlder).Methods(http.MethodGet)
// 将静态文件服务放在最后,作为默认处理程序
router.PathPrefix("/").Handler(http.NotFoundHandler())
if staticRouter != nil {

12
godo/files/fs.go

@ -16,13 +16,11 @@ import (
)
func HandleSystemInfo(w http.ResponseWriter, r *http.Request) {
info := libs.GetSystemInfo()
// res := libs.APIResponse{
// Message: "File information retrieved successfully.",
// Data: info,
// }
//json.NewEncoder(w).Encode(res)
info, err := libs.GetSystemInfo()
if err != nil {
libs.ErrorMsg(w, "Failed to retrieve file information.")
return
}
libs.SuccessMsg(w, info, "File information retrieved successfully.")
}

37
godo/libs/config.go

@ -11,10 +11,8 @@ import (
var reqBodyMap = sync.Map{}
type ReqBody struct {
Name string `json:"name"`
Value string `json:"value"`
Type string `json:"type"`
Info map[string]string `json:"info"`
Name string `json:"name"`
Value any `json:"value"`
}
func GetConfigFile() (string, error) {
@ -25,7 +23,8 @@ func GetConfigFile() (string, error) {
if !PathExists(baseDir) {
os.MkdirAll(baseDir, 0755)
}
configFile := filepath.Join(baseDir, "config.json")
configFile := filepath.Join(baseDir, "os_config.json")
//log.Printf("config file path: %s", configFile)
if !PathExists(configFile) {
// 如果文件不存在,则创建一个空的配置文件
err := os.WriteFile(configFile, []byte("[]"), 0644)
@ -89,13 +88,12 @@ func SaveConfig() error {
}
return nil
}
func GetConfig(Name string) (ReqBody, bool) {
func GetConfig(Name string) (any, bool) {
value, ok := reqBodyMap.Load(Name)
if ok {
return value.(ReqBody), true
return value.(ReqBody).Value, true
}
return ReqBody{}, false
return "", false
}
func ExistConfig(Name string) bool {
_, exists := reqBodyMap.Load(Name)
@ -104,14 +102,31 @@ func ExistConfig(Name string) bool {
func SetConfig(reqBody ReqBody) error {
reqBodyMap.Store(reqBody.Name, reqBody)
//log.Println("=====SetName", reqBody.Name)
if err := SaveConfig(); err != nil {
return fmt.Errorf("failed to save updated configuration: %w", err)
}
return nil
}
func SetConfigByName(Name string, Value any) error {
// 尝试从 reqBodyMap 中加载 Name
value, ok := reqBodyMap.Load(Name)
if !ok {
// 如果 Name 不存在,则创建一个新的 ReqBody
newReqBody := ReqBody{Name: Name, Value: Value}
reqBodyMap.Store(Name, newReqBody)
} else {
// 如果 Name 存在,则更新现有 ReqBody 的 Value
existingReqBody, _ := value.(ReqBody)
existingReqBody.Value = Value
reqBodyMap.Store(Name, existingReqBody)
}
if err := SaveConfig(); err != nil {
return fmt.Errorf("failed to save updated configuration: %w", err)
}
return nil
}
func SetConfigs(reqBody []ReqBody) error {
for _, rb := range reqBody {
reqBodyMap.Store(rb.Name, rb)

25
godo/libs/dir.go

@ -12,19 +12,23 @@ func Initdir() error {
if err != nil {
return err
}
exist := ExistConfig("osInfo")
exist := ExistConfig("osPath")
if !exist {
osDir, err := InitOsDir()
if err != nil {
return err
}
info := GetSystemInfo()
osData := ReqBody{
Name: "osInfo",
Name: "osPath",
Value: osDir,
Info: info,
}
SetConfig(osData)
info := GenerateSystemInfo()
osInfo := ReqBody{
Name: "osInfo",
Value: info,
}
SetConfig(osInfo)
}
return nil
}
@ -40,14 +44,13 @@ func InitOsDir() (string, error) {
return osDir, nil
}
func GetOsDir() (string, error) {
osDirInfo, _ := GetConfig("osInfo")
res := osDirInfo.Value
//log.Printf("=====osInfo: %s", res)
// if osDirInfo.UserType == "member" {
// res = filepath.Join(res, osDirInfo.UserName)
// }
return res, nil
osDir, ok := GetConfig("osPath")
if !ok {
return "", fmt.Errorf("osPath not found")
}
return osDir.(string), nil
}
func GetAppDir() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {

216
godo/libs/info.go

@ -1,98 +1,64 @@
package libs
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"net"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/mem"
)
// SystemInfo 定义系统信息结构体
type SystemInfo struct {
MAC string `json:"mac"`
CPUUsage float64 `json:"cpu_usage"`
OS string `json:"os"`
Arch string `json:"arch"`
MemoryTotal string `json:"memory_total"`
// UserOsInfo 定义系统信息结构体
type UserOsInfo struct {
MAC string `json:"mac"`
OS string `json:"os"`
Arch string `json:"arch"`
AppName string `json:"app_name"`
Hostname string `json:"hostname"`
TopType string `json:"toptype"`
UseType string `json:"usetype"`
SourceType string `json:"sourcetype"`
}
// GetSystemInfo 获取系统信息
// GetSystemInfo 获取系统综合信息
func GetSystemInfo() map[string]string {
info := make(map[string]string)
// 获取本机IP地址
ip, err := getIPAddress()
if err == nil {
info["ip"] = ip
} else {
info["ip"] = ""
}
func GenerateSystemInfo() UserOsInfo {
info := UserOsInfo{}
// 获取MAC地址
mac, err := getMACAddress()
if err == nil {
info["mac"] = mac
} else {
info["mac"] = ""
}
// 获取CPU信息
cpuPercent, err := cpu.Percent(time.Second, false)
if err == nil {
info["cpu"] = strconv.FormatFloat(cpuPercent[0], 'f', 2, 64) + "%"
} else {
info["cpu"] = ""
}
// 获取内存信息
memInfo, err := mem.VirtualMemory()
if err == nil {
info["memory_total"] = byteCountSI(memInfo.Total)
info["memory_used"] = byteCountSI(memInfo.Used)
info["memory_free"] = byteCountSI(memInfo.Free)
info.MAC = mac
} else {
info["memory_total"] = ""
info["memory_used"] = ""
info["memory_free"] = ""
info.MAC = ""
}
// 获取主机名
hostname, err := os.Hostname()
if err == nil {
info["hostname"] = hostname
info.Hostname = hostname
} else {
info["hostname"] = ""
info.Hostname = ""
}
// 获取操作系统和架构信息
info["os"] = runtime.GOOS
info["arch"] = runtime.GOARCH
info.OS = runtime.GOOS
info.Arch = runtime.GOARCH
// 获取系统运行时间
uptime, err := host.Uptime()
if err == nil {
info["uptime_seconds"] = strconv.FormatUint(uptime, 10)
} else {
info["uptime_seconds"] = ""
info.AppName = "godoos"
info.TopType = os.Getenv("GODOTOPTYPE")
if info.TopType == "" {
info.TopType = "web"
}
info.UseType = "person"
info.SourceType = "open"
return info
}
// getIPAddress 获取本机IP地址
func getIPAddress() (string, error) {
// GetIPAddress 获取本机IP地址
func GetIPAddress() (string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
@ -118,128 +84,40 @@ func getMACAddress() (string, error) {
if err != nil {
return "", err
}
var macs string
// 记录所有接口的信息
for _, iface := range ifaces {
if iface.Flags&net.FlagUp != 0 && (strings.HasPrefix(iface.Name, "en") || strings.HasPrefix(iface.Name, "eth")) {
return iface.HardwareAddr.String(), nil
if iface.HardwareAddr != nil {
macs += iface.HardwareAddr.String()
}
}
return "", fmt.Errorf("no active Ethernet interface found")
//返回md5加密数据
return Md5Encrypt(macs), nil
}
// byteCountSI 格式化字节大小为可读字符串
func byteCountSI(b uint64) string {
const unit = 1000
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
func Md5Encrypt(s string) string {
hasher := md5.New()
hasher.Write([]byte(s))
return hex.EncodeToString(hasher.Sum(nil))
}
// GenerateBase64Info 生成基于mac、cpu、os、arch信息的Base64编码字符串
func GenerateBase64Info() (string, error) {
// 获取必要的系统信息
cpuPercent, err := cpu.Percent(time.Second, false)
if err != nil {
return "", err
}
memInfo, err := mem.VirtualMemory()
if err != nil {
return "", err
}
// 获取MAC地址
mac, err := getMACAddress()
if err == nil {
return "", err
}
// GetSystemInfo 生成基于mac、os、arch信息的Base64编码字符串
func GetSystemInfo() (string, error) {
// 构造系统信息对象
systemInfo := SystemInfo{
MAC: mac,
CPUUsage: cpuPercent[0],
OS: runtime.GOOS,
Arch: runtime.GOARCH,
MemoryTotal: byteCountSI(memInfo.Total),
lineseInfo, ok := GetConfig("osInfo")
if !ok {
return "", fmt.Errorf("未找到osInfo配置")
}
systemInfo, ok := lineseInfo.(UserOsInfo)
if !ok {
return "", fmt.Errorf("osInfo配置错误")
}
// 将系统信息序列化为JSON字符串
jsonBytes, err := json.Marshal(systemInfo)
if err != nil {
return "", err
}
// 对JSON字符串进行Base64编码
encodedInfo := base64.StdEncoding.EncodeToString(jsonBytes)
return encodedInfo, nil
}
// DecryptWithAES 使用AES解密数据
func DecryptWithAES(ciphertext []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(ciphertext) < aes.BlockSize {
return nil, fmt.Errorf("ciphertext too short")
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(ciphertext, ciphertext)
return ciphertext, nil
}
// VerifyAndDecrypt 验证时间戳并解密数据
func VerifyAndDecrypt(encodedInfo string, startTime time.Time, endTime time.Time, flag string) (*SystemInfo, error) {
// Base64解码
ciphertext, err := base64.StdEncoding.DecodeString(encodedInfo)
if err != nil {
return nil, err
}
// 生成AES密钥
aesKey, err := GenerateAESKey(startTime, endTime, flag)
if err != nil {
return nil, err
}
// 解密数据
decryptedBytes, err := DecryptWithAES(ciphertext, aesKey)
if err != nil {
return nil, err
}
// 反序列化JSON为SystemInfo
var systemInfo SystemInfo
if err := json.Unmarshal(decryptedBytes, &systemInfo); err != nil {
return nil, err
}
// 获取当前系统MAC地址
currentMAC, err := getMACAddress()
if err != nil {
return nil, fmt.Errorf("failed to get current MAC address: %w", err)
}
// 检查解密后的MAC地址是否与当前系统MAC地址一致
if systemInfo.MAC != currentMAC {
return nil, fmt.Errorf("decrypted MAC address does not match the current system's MAC address")
}
if systemInfo.OS != runtime.GOOS {
return nil, fmt.Errorf("decrypted GOOS does not match the current system's runtime.GOOS")
}
// 验证时间有效性
currentTime := time.Now()
if currentTime.Before(startTime) || currentTime.After(endTime) {
return nil, fmt.Errorf("the provided time window is invalid")
}
return &systemInfo, nil
}

61
godo/libs/key.go

@ -1,61 +0,0 @@
package libs
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"time"
)
// GenerateAESKey 生成AES密钥,基于时间戳和标志字符串
func GenerateAESKey(startTime time.Time, endTime time.Time, flag string) ([]byte, error) {
// 这里简单地将时间戳和标志字符串连接起来生成密钥,实际应用中应使用更安全的方式生成密钥
keyMaterial := fmt.Sprintf("%d-%d-%s", startTime.Unix(), endTime.Unix(), flag)
return []byte(keyMaterial)[:16], nil // AES-128需要16字节的密钥
}
// EncryptWithAES 使用AES加密数据
func EncryptWithAES(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
ciphertext := make([]byte, aes.BlockSize+len(data))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], data)
return ciphertext, nil
}
func GenerateEncryptedJSONInfo(startTime time.Time, endTime time.Time, flag string, systemInfoBase64String string) (string, error) {
// 解析Base64编码的系统信息字符串为字节切片
decodedBytes, err := base64.StdEncoding.DecodeString(systemInfoBase64String)
if err != nil {
return "", err
}
// 生成AES密钥
aesKey, err := GenerateAESKey(startTime, endTime, flag)
if err != nil {
return "", err
}
// 使用AES加密JSON数据
encryptedBytes, err := EncryptWithAES(decodedBytes, aesKey)
if err != nil {
return "", err
}
// 对加密后的数据进行Base64编码以便于传输和存储
encodedInfo := base64.StdEncoding.EncodeToString(encryptedBytes)
return encodedInfo, nil
}

21
godo/localchat/sse.go

@ -3,8 +3,10 @@ package localchat
import (
"encoding/json"
"fmt"
"godo/libs"
"log"
"net/http"
"os"
"time"
)
@ -110,3 +112,22 @@ func HandleMessage(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Text message send successfully")
}
func CheckUserHanlder(w http.ResponseWriter, r *http.Request) {
res := map[string]any{}
res["code"] = 0
res["message"] = "ok"
// 获取主机名
hostname, err := os.Hostname()
if err == nil {
hostname = "Unknown"
}
ip, _ := libs.GetIPAddress()
res["data"] = map[string]any{
"ip": ip,
"hostname": hostname,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(res)
}

65
godo/sys/setting.go

@ -2,48 +2,61 @@ package sys
import (
"encoding/json"
"fmt"
"godo/libs"
"net"
"net/http"
"os"
)
func GetOsPath() string {
osInfo, _ := libs.GetConfig("osInfo")
return osInfo.Value
}
func HandleSetConfig(w http.ResponseWriter, r *http.Request) {
var req libs.ReqBody
err := json.NewDecoder(r.Body).Decode(&req)
func ConfigHandler(w http.ResponseWriter, r *http.Request) {
var reqs []libs.ReqBody
err := json.NewDecoder(r.Body).Decode(&reqs)
if err != nil {
libs.ErrorMsg(w, "The params is error!")
return
}
if req.Name == "osInfo" && req.Value != "" {
osInfo, _ := libs.GetConfig("osInfo")
if osInfo.Value != req.Value {
if !libs.PathExists(req.Value) {
libs.ErrorMsg(w, "The Path is not exists!")
return
for _, req := range reqs {
if req.Name == "osPath" {
reqPath := req.Value.(string)
osPath, ok := libs.GetConfig("osPath")
if !ok || osPath != reqPath {
if !libs.PathExists(reqPath) {
libs.ErrorMsg(w, "The Path is not exists!")
return
}
err = os.Chmod(reqPath, 0755)
if err != nil {
libs.ErrorMsg(w, "The Path chmod is error!")
return
}
libs.SetConfig(req)
}
err = os.Chmod(req.Value, 0755)
}
if req.Name == "ipList" {
err = SetIplist(req)
if err != nil {
libs.ErrorMsg(w, "The Path chmod is error!")
libs.ErrorMsg(w, err.Error())
return
}
osInfo.Value = req.Value
osInfo.Type = req.Type
libs.SetConfig(osInfo)
}
}
libs.SuccessMsg(w, "success", "The config set success!")
}
func SetIplist(req libs.ReqBody) error {
reqIplist, ok := req.Value.([]string)
if !ok {
return fmt.Errorf("unexpected type for iplist:%v", reqIplist)
}
if req.Name == "userInfo" ||
req.Name == "dbInfo" {
if len(reqIplist) > 0 {
for _, ip := range reqIplist {
_, _, err := net.ParseCIDR(ip)
if err != nil {
return fmt.Errorf("the iplist is error:%s", err.Error())
}
}
libs.SetConfig(req)
}
err = libs.LoadConfig()
if err != nil {
libs.ErrorMsg(w, "The config load error!")
return
}
libs.SuccessMsg(w, "success", "The config set success!")
return nil
}

2
main.go

@ -2,6 +2,7 @@ package main
import (
"embed"
"os"
App "godoos/app"
@ -16,6 +17,7 @@ var assets embed.FS
func main() {
// Create an instance of the app structure
app := App.NewApp()
os.Setenv("GODOTOPTYPE", "desktop")
// Create application with options
err := wails.Run(&options.App{
Title: "GodoOS",

Loading…
Cancel
Save