v1.0.0
3
.commitlintrc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
}
|
7
.dockerignore
Normal file
@ -0,0 +1,7 @@
|
||||
**/node_modules
|
||||
*/node_modules
|
||||
node_modules
|
||||
Dockerfile
|
||||
.*
|
||||
*/.*
|
||||
!.env
|
11
.editorconfig
Normal file
@ -0,0 +1,11 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
10
.env
Normal file
@ -0,0 +1,10 @@
|
||||
# Glob API URL
|
||||
VITE_GLOB_API_URL=/api
|
||||
|
||||
VITE_APP_API_BASE_URL=http://127.0.0.1:3002/
|
||||
|
||||
# Whether long replies are supported, which may result in higher API fees
|
||||
VITE_GLOB_OPEN_LONG_REPLY=false
|
||||
|
||||
# When you want to use PWA
|
||||
VITE_GLOB_APP_PWA=false
|
3
.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
docker-compose
|
||||
kubernetes
|
||||
/service
|
7
.eslintrc.cjs
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@antfu'],
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', // 取消打印标红提醒
|
||||
},
|
||||
}
|
17
.gitattributes
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
"*.vue" eol=lf
|
||||
"*.js" eol=lf
|
||||
"*.ts" eol=lf
|
||||
"*.jsx" eol=lf
|
||||
"*.tsx" eol=lf
|
||||
"*.cjs" eol=lf
|
||||
"*.cts" eol=lf
|
||||
"*.mjs" eol=lf
|
||||
"*.mts" eol=lf
|
||||
"*.json" eol=lf
|
||||
"*.html" eol=lf
|
||||
"*.css" eol=lf
|
||||
"*.less" eol=lf
|
||||
"*.scss" eol=lf
|
||||
"*.sass" eol=lf
|
||||
"*.styl" eol=lf
|
||||
"*.md" eol=lf
|
42
.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Environment variables files
|
||||
/service/.env
|
||||
.history
|
||||
service_node
|
||||
|
||||
bindata.go
|
||||
service/assets/bindata.go
|
||||
service/conf
|
||||
service/lang
|
||||
service/database
|
||||
service/web
|
||||
service/uploads
|
4
.husky/commit-msg
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no -- commitlint --edit
|
4
.husky/pre-commit
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar", "dbaeumer.vscode-eslint"]
|
||||
}
|
66
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"json",
|
||||
"jsonc",
|
||||
"json5",
|
||||
"yaml",
|
||||
"yml",
|
||||
"markdown"
|
||||
],
|
||||
"cSpell.words": [
|
||||
"antfu",
|
||||
"axios",
|
||||
"bumpp",
|
||||
"chatgpt",
|
||||
"chenzhaoyu",
|
||||
"commitlint",
|
||||
"davinci",
|
||||
"dockerhub",
|
||||
"esno",
|
||||
"GPTAPI",
|
||||
"highlightjs",
|
||||
"hljs",
|
||||
"iconify",
|
||||
"katex",
|
||||
"katexmath",
|
||||
"linkify",
|
||||
"logprobs",
|
||||
"mdhljs",
|
||||
"mila",
|
||||
"nodata",
|
||||
"OPENAI",
|
||||
"pinia",
|
||||
"Popconfirm",
|
||||
"rushstack",
|
||||
"Sider",
|
||||
"tailwindcss",
|
||||
"traptitech",
|
||||
"tsup",
|
||||
"Typecheck",
|
||||
"unplugin",
|
||||
"VITE",
|
||||
"vueuse",
|
||||
"Zhao"
|
||||
],
|
||||
"i18n-ally.enabledParsers": [
|
||||
"ts"
|
||||
],
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.keepFulfilled": true,
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/locales"
|
||||
],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"vue.codeActions.enabled": false
|
||||
}
|
47
Dockerfile
Normal file
@ -0,0 +1,47 @@
|
||||
# build front-end
|
||||
FROM node:lts-alpine AS web_image
|
||||
|
||||
RUN npm install pnpm -g
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY ./package.json /build
|
||||
|
||||
COPY ./pnpm-lock.yaml /build
|
||||
|
||||
RUN pnpm install
|
||||
|
||||
COPY . /build
|
||||
|
||||
RUN pnpm run build
|
||||
|
||||
# build backend
|
||||
FROM golang:1.19 as server_image
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY ./service .
|
||||
|
||||
|
||||
# 执行指令 关闭链接确认
|
||||
RUN go env -w GO111MODULE=on \
|
||||
&& go env -w GOPROXY=https://goproxy.cn,direct \
|
||||
&& export PATH=$PATH:/go/bin \
|
||||
&& go install -a -v github.com/go-bindata/go-bindata/...@latest \
|
||||
&& go install -a -v github.com/elazarl/go-bindata-assetfs/...@latest \
|
||||
&& go-bindata-assetfs -o=assets/bindata.go -pkg=assets assets/... \
|
||||
&& go build -o sun-panel --ldflags="-X sun-panel/global.RUNCODE=release -X sun-panel/global.ISDOCKER=docker" main.go
|
||||
|
||||
|
||||
# run_image
|
||||
FROM ubuntu
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=web_image /build/dist /app/web
|
||||
|
||||
COPY --from=server_image /build/sun-panel /app/sun-panel
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates &&./sun-panel -config
|
||||
|
||||
CMD ./sun-panel
|
96
README.md
Normal file
@ -0,0 +1,96 @@
|
||||
|
||||
# Sun-Panel
|
||||
一个NAS导航面板、Homepage、浏览器首页。
|
||||
|
||||
## 特点
|
||||
|
||||
- 局域网内外网链接切换
|
||||
- 简洁
|
||||
- docker 部署
|
||||
- 上手简单,免修改代码
|
||||
- 无需连接外部数据库
|
||||
- 丰富图标自由搭配(文字图标+svg图标+内置三方图标库)
|
||||
- 支持网页内置小窗口打开(部分网站屏蔽此功能)
|
||||
|
||||
|
||||
|
||||
## 后面想做的事
|
||||
|
||||
随心开发
|
||||
|
||||
- [ ] 图标排序
|
||||
- [ ] 服务器监控
|
||||
- [ ] docker管理器
|
||||
- [ ] 计划任务
|
||||
|
||||
## 预览截图
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
内置小窗口
|
||||

|
||||

|
||||
|
||||
## 使用教程
|
||||
|
||||
### docker 运行
|
||||
|
||||
目录挂载 `-v`,根据自己的需求选择:
|
||||
|容器目录|说明|
|
||||
|---|---|
|
||||
|/app/conf|配置文件|
|
||||
|/app/uploads|上传的文件|
|
||||
|/app/database|数据库文件|
|
||||
|/app/runtime|运行日志(不推荐挂载)|
|
||||
|
||||
1. 拉取镜像
|
||||
```
|
||||
docker pull hslr/sun-panel
|
||||
```
|
||||
|
||||
2. 直接下载运行
|
||||
```
|
||||
docker run -d --restart=always -p 3002:3002 \
|
||||
-v ~/docker_data/sun-panel/conf:/app/conf \
|
||||
-v ~/docker_data/sun-panel/uploads:/app/uploads \
|
||||
-v ~/docker_data/sun-panel/database:/app/database \
|
||||
--name sun-panel \
|
||||
hslr/sun-panel
|
||||
```
|
||||
|
||||
|
||||
### 编译和运行
|
||||
|
||||
前端
|
||||
```
|
||||
# 开发运行
|
||||
pnpm dev
|
||||
|
||||
# 编译打包
|
||||
pnpm build
|
||||
```
|
||||
|
||||
后端
|
||||
```
|
||||
# 开发运行
|
||||
|
||||
cd service
|
||||
|
||||
# 开发运行
|
||||
go run main.go
|
||||
|
||||
# 编译打包
|
||||
go build -o sun-panel main.go
|
||||
```
|
||||
|
||||
docker windows本地开发编译运行
|
||||
|
||||
```
|
||||
// 编译
|
||||
docker build -t sun-panel .
|
||||
|
||||
// 运行 D:\docker\data\sun-panel 为本地运行的路径
|
||||
docker run --rm -d -p 3003:3002 -v D:\docker\data\sun-panel\conf:/app/conf -v D:\docker\data\sun-panel\runtime:/app/runtime -v D:\docker\data\sun-panel\uploads:/app/uploads -v D:\docker\data\sun-panel\database:/app/database --name sun-panel sun-panel
|
||||
```
|
1
config/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './proxy'
|
16
config/proxy.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { ProxyOptions } from 'vite'
|
||||
|
||||
export function createViteProxy(isOpenProxy: boolean, viteEnv: ImportMetaEnv) {
|
||||
if (!isOpenProxy)
|
||||
return
|
||||
|
||||
const proxy: Record<string, string | ProxyOptions> = {
|
||||
'/api': {
|
||||
target: viteEnv.VITE_APP_API_BASE_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace('/api/', '/'),
|
||||
},
|
||||
}
|
||||
|
||||
return proxy
|
||||
}
|
BIN
doc/images/full-color-info.jpg
Normal file
After Width: | Height: | Size: 496 KiB |
BIN
doc/images/full-color-small.jpg
Normal file
After Width: | Height: | Size: 488 KiB |
BIN
doc/images/icon-info.jpg
Normal file
After Width: | Height: | Size: 236 KiB |
BIN
doc/images/icon-small.jpg
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
doc/images/window-ssh.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
doc/images/window-xunlei.png
Normal file
After Width: | Height: | Size: 1.6 MiB |
83
index.html
Normal file
@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cmn-Hans">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<meta content="yes" name="apple-mobile-web-app-capable"/>
|
||||
<link rel="apple-touch-icon" href="/favicon.ico">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
|
||||
<title>Sun Panel</title>
|
||||
</head>
|
||||
|
||||
<body class="dark:bg-black">
|
||||
<div id="app">
|
||||
<style>
|
||||
.loading-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.balls {
|
||||
width: 4em;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.balls div {
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
border-radius: 50%;
|
||||
background-color: #4b9e5f;
|
||||
}
|
||||
|
||||
.balls div:nth-of-type(1) {
|
||||
transform: translateX(-100%);
|
||||
animation: left-swing 0.5s ease-in alternate infinite;
|
||||
}
|
||||
|
||||
.balls div:nth-of-type(3) {
|
||||
transform: translateX(-95%);
|
||||
animation: right-swing 0.5s ease-out alternate infinite;
|
||||
}
|
||||
|
||||
@keyframes left-swing {
|
||||
|
||||
50%,
|
||||
100% {
|
||||
transform: translateX(95%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes right-swing {
|
||||
50% {
|
||||
transform: translateX(-95%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #121212;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="loading-wrap">
|
||||
<div class="balls">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
19570
package-lock.json
generated
Normal file
71
package.json
Normal file
@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "chatgpt-web",
|
||||
"version": "2.10.9",
|
||||
"private": false,
|
||||
"description": "ChatGPT Web",
|
||||
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",
|
||||
"keywords": [
|
||||
"chatgpt-web",
|
||||
"chatgpt",
|
||||
"chatbot",
|
||||
"vue"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check build-only",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"bootstrap": "pnpm install && pnpm run common:prepare",
|
||||
"common:cleanup": "rimraf node_modules && rimraf pnpm-lock.yaml",
|
||||
"common:prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@traptitech/markdown-it-katex": "^3.6.0",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"highlight.js": "^11.7.0",
|
||||
"katex": "^0.16.4",
|
||||
"markdown-it": "^13.0.1",
|
||||
"moment": "^2.29.4",
|
||||
"naive-ui": "^2.34.3",
|
||||
"pinia": "^2.0.33",
|
||||
"vue": "^3.2.47",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.1.6",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.35.3",
|
||||
"@commitlint/cli": "^17.4.4",
|
||||
"@commitlint/config-conventional": "^17.4.4",
|
||||
"@iconify/vue": "^4.1.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/katex": "^0.16.0",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/markdown-it-link-attributes": "^3.0.1",
|
||||
"@types/node": "^18.14.6",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"axios": "^1.3.4",
|
||||
"crypto-js": "^4.1.1",
|
||||
"eslint": "^8.35.0",
|
||||
"less": "^4.1.3",
|
||||
"lint-staged": "^13.1.2",
|
||||
"markdown-it-link-attributes": "^4.0.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.21",
|
||||
"rimraf": "^4.2.0",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"typescript": "~4.9.5",
|
||||
"vite": "^4.2.0",
|
||||
"vite-plugin-pwa": "^0.14.4",
|
||||
"vue-tsc": "^1.2.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx,vue}": [
|
||||
"pnpm lint:fix"
|
||||
]
|
||||
}
|
||||
}
|
6961
pnpm-lock.yaml
generated
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 162 KiB |
1
public/favicon.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"><svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none"><path d="M663 1649 c-131 -41 -254 -157 -304 -286 -33 -86 -38 -221 -11 -310 36 -117 97 -200 196 -265 108 -70 153 -78 438 -78 l250 0 29 29 c38 39 40 97 5 138 l-24 28 -239 5 c-270 7 -300 13 -370 74 -149 131 -116 368 62 452 l50 24 463 0 462 0 0 105 0 105 -472 -1 c-442 0 -477 -1 -535 -20z"/><path d="M739 1261 c-38 -39 -40 -97 -5 -138 l24 -28 239 -5 c270 -7 300 -13 370 -74 150 -132 115 -373 -67 -455 -43 -20 -64 -21 -507 -21 l-463 0 0 -105 0 -105 469 0 c443 0 472 1 535 20 133 42 256 156 307 287 33 86 38 221 11 310 -23 74 -81 171 -128 214 -49 45 -126 89 -193 109 -56 18 -93 20 -313 20 l-250 0 -29 -29z"/></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
15
service/api/api_v1/A_ENTER.go
Normal file
@ -0,0 +1,15 @@
|
||||
package api_v1
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/openness"
|
||||
"sun-panel/api/api_v1/panel"
|
||||
"sun-panel/api/api_v1/system"
|
||||
)
|
||||
|
||||
type ApiGroup struct {
|
||||
ApiSystem system.ApiSystem // 系统功能api
|
||||
ApiOpen openness.ApiPpenness
|
||||
ApiPanel panel.ApiPanel
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
@ -0,0 +1,5 @@
|
||||
package adminApiStructs
|
||||
|
||||
type AboutSettingRequest struct {
|
||||
Content string `json:"content"`
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package adminApiStructs
|
||||
|
||||
type GetStatisticsResp struct {
|
||||
UserCount int64 `json:"userCount"`
|
||||
UserToday int64 `json:"userToday"`
|
||||
RoleCount int64 `json:"roleCount"`
|
||||
RoleToday int64 `json:"roleToday"`
|
||||
DialogCount int64 `json:"dialogCount"`
|
||||
DialogToday int64 `json:"dialogToday"`
|
||||
DrawCount int64 `json:"drawCount"`
|
||||
DrawToday int64 `json:"drawToday"`
|
||||
}
|
22
service/api/api_v1/common/apiData/commonApiStructs/common.go
Normal file
@ -0,0 +1,22 @@
|
||||
package commonApiStructs
|
||||
|
||||
type RequestPage struct {
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
Keyword string `json:"keyword"`
|
||||
}
|
||||
|
||||
type RequestDeleteIds[T int | uint] struct {
|
||||
Ids []T `json:"ids"`
|
||||
}
|
||||
|
||||
type VerificationRequest struct {
|
||||
CodeID string `json:"codeId"`
|
||||
VCode string `json:"vCode"`
|
||||
}
|
||||
|
||||
type VerificationResponse struct {
|
||||
CodeID string `json:"codeId"`
|
||||
Result bool `json:"result"`
|
||||
Message string `json:"message"`
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package adminApiStructs
|
||||
|
||||
import "sun-panel/models"
|
||||
|
||||
type ItemIconEditRequest struct {
|
||||
models.ItemIcon
|
||||
IconJson string
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package systemApiStructs
|
||||
|
||||
type NoticeGetListByDisplayTypeReq struct {
|
||||
DisplayType []int `json:"displayType"`
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package systemApiStructs
|
||||
|
||||
type GetReferralCodeResp struct {
|
||||
ReferralCode string `json:"referralCode"`
|
||||
}
|
125
service/api/api_v1/common/apiReturn/apiReturn.go
Normal file
@ -0,0 +1,125 @@
|
||||
package apiReturn
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiData/commonApiStructs"
|
||||
"sun-panel/global"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const ERROR_CODE_SUCCESS = 0 // 错误码 无任何错误
|
||||
|
||||
const (
|
||||
// 验证器类
|
||||
|
||||
ERROR_CODE_VERIFICATION_MUST = 1101 // 错误码 验证器类:必须需要验证或者验证数据为空
|
||||
ERROR_CODE_VERIFICATION_FAIL = 1102 // 错误码 验证器类:验证失败,验证错误
|
||||
|
||||
// 数据类
|
||||
|
||||
ERROR_CODE_DATA_DATABASE = 1110 // 错误码 数据类:数据库报错
|
||||
ERROR_CODE_DATA_RECORD_NOT_FOUND = 1111 // 错误码 数据类:数据记录未找到
|
||||
)
|
||||
|
||||
func ApiReturn(ctx *gin.Context, code int, msg string, data interface{}) {
|
||||
returnData := map[string]interface{}{
|
||||
"code": code,
|
||||
"msg": msg,
|
||||
}
|
||||
if data != nil {
|
||||
returnData["data"] = data
|
||||
}
|
||||
ctx.JSON(200, returnData)
|
||||
}
|
||||
|
||||
// 返回成功
|
||||
func SuccessData(ctx *gin.Context, data interface{}) {
|
||||
ApiReturn(ctx, 0, "OK", data)
|
||||
}
|
||||
|
||||
// 返回列表
|
||||
func SuccessListData(ctx *gin.Context, list interface{}, count int64) {
|
||||
ApiReturn(ctx, 0, "OK", gin.H{
|
||||
"list": list,
|
||||
"count": count,
|
||||
})
|
||||
}
|
||||
|
||||
// 返回成功,没有data数据
|
||||
func Success(ctx *gin.Context) {
|
||||
ApiReturn(ctx, 0, "OK", nil)
|
||||
}
|
||||
|
||||
// 返回列表数据
|
||||
func ListData(ctx *gin.Context, list interface{}, count int64) {
|
||||
data := map[string]interface{}{
|
||||
"list": list,
|
||||
"count": count,
|
||||
}
|
||||
ApiReturn(ctx, 0, "OK", data)
|
||||
}
|
||||
|
||||
// 返回错误 验证码相关错误错误
|
||||
func ErrorVerification(ctx *gin.Context, errCode int, codeID string) {
|
||||
msg := ""
|
||||
switch errCode {
|
||||
case ERROR_CODE_VERIFICATION_FAIL:
|
||||
msg = "验证失败,请重新验证"
|
||||
case ERROR_CODE_VERIFICATION_MUST:
|
||||
msg = "需要进一步验证"
|
||||
}
|
||||
ApiReturn(ctx, errCode, msg, gin.H{
|
||||
"verification": commonApiStructs.VerificationResponse{
|
||||
CodeID: codeID,
|
||||
Result: false,
|
||||
Message: msg,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 返回错误 需要个性化定义的错误|带返回数据的错误
|
||||
func ErrorCode(ctx *gin.Context, code int, errMsg string, data interface{}) {
|
||||
ApiReturn(ctx, code, errMsg, data)
|
||||
}
|
||||
|
||||
// 返回错误 普通提示错误
|
||||
func Error(ctx *gin.Context, errMsg string) {
|
||||
ErrorCode(ctx, -1, errMsg, nil)
|
||||
}
|
||||
|
||||
// 返回错误 需要个性化定义的错误|带返回数据的错误
|
||||
func ErrorNoAccess(ctx *gin.Context) {
|
||||
ErrorCode(ctx, 1005, global.Lang.Get("common.no_access"), nil)
|
||||
}
|
||||
|
||||
// 返回错误 参数错误
|
||||
func ErrorParamFomat(ctx *gin.Context, errMsg string) {
|
||||
Error(ctx, global.Lang.GetAndInsert("common.api_error_param_format", "[", errMsg, "]"))
|
||||
// Error(ctx, "参数错误")
|
||||
}
|
||||
|
||||
// // 返回错误 数据库
|
||||
func ErrorDatabase(ctx *gin.Context, errMsg string) {
|
||||
Error(ctx, global.Lang.GetAndInsert("common.db_error", "[", errMsg, "]"))
|
||||
}
|
||||
|
||||
// 返回错误 数据记录未找到
|
||||
func ErrorDataNotFound(ctx *gin.Context) {
|
||||
// ErrorCode(ctx,, global.Lang.GetAndInsert("common.db_error", "[", errMsg, "]"))
|
||||
ErrorCode(ctx, ERROR_CODE_DATA_RECORD_NOT_FOUND, "未找到数据记录", nil)
|
||||
}
|
||||
|
||||
// 返回错误 需要个性化定义的错误|带返回数据的错误
|
||||
// func ErrorNoAccess(ctx *gin.Context) {
|
||||
// ErrorCode(ctx, 1005, global.Lang.Get("common.no_access"), nil)
|
||||
// }
|
||||
|
||||
// // 返回错误 参数错误
|
||||
// func ErrorParamFomat(ctx *gin.Context, errMsg string) {
|
||||
// Error(ctx, global.Lang.GetAndInsert("common.api_error_param_format", "[", errMsg, "]"))
|
||||
// }
|
||||
|
||||
// // 返回错误 数据库
|
||||
// func ErrorDatabase(ctx *gin.Context, errMsg string) {
|
||||
// Error(ctx, global.Lang.GetAndInsert("common.db_error", "[", errMsg, "]"))
|
||||
// }
|
99
service/api/api_v1/common/base/base.go
Normal file
@ -0,0 +1,99 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/lib/captcha"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/locales/zh"
|
||||
ut "github.com/go-playground/universal-translator"
|
||||
"github.com/go-playground/validator/v10"
|
||||
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
|
||||
)
|
||||
|
||||
type PageLimitVerify struct {
|
||||
Page int64
|
||||
Limit int64
|
||||
}
|
||||
|
||||
// 验证输入是否有效并返回错误
|
||||
func validateInputStruct(params interface{}) (errMsg string, err error) {
|
||||
var validate = validator.New()
|
||||
//通过label标签返回自定义错误内容
|
||||
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
|
||||
label := field.Tag.Get("label")
|
||||
if label == "" {
|
||||
return field.Name
|
||||
}
|
||||
return label
|
||||
})
|
||||
|
||||
// 自定义验证规则,使用 strings.TrimSpace 函数删除前后空格
|
||||
validate.RegisterValidation("trimmedRequired", func(fl validator.FieldLevel) bool {
|
||||
return strings.TrimSpace(fl.Field().String()) != ""
|
||||
})
|
||||
|
||||
if err = validate.Struct(params); err != nil {
|
||||
trans := validateTransInit(validate)
|
||||
verrs := err.(validator.ValidationErrors)
|
||||
// errs := make(map[string]string)
|
||||
for _, value := range verrs.Translate(trans) {
|
||||
// errs[key[strings.Index(key, ".")+1:]] = value
|
||||
errMsg += " " + value
|
||||
}
|
||||
// fmt.Println(errs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 验证输入是否有效并返回错误
|
||||
func ValidateInputStruct(params interface{}) (errMsg string, err error) {
|
||||
return validateInputStruct(params)
|
||||
}
|
||||
|
||||
// 数据验证翻译器
|
||||
func validateTransInit(validate *validator.Validate) ut.Translator {
|
||||
// 万能翻译器,保存所有的语言环境和翻译数据
|
||||
uni := ut.New(zh.New())
|
||||
// 翻译器
|
||||
trans, _ := uni.GetTranslator("zh")
|
||||
//验证器注册翻译器
|
||||
err := zhTranslations.RegisterDefaultTranslations(validate, trans)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return trans
|
||||
}
|
||||
|
||||
func GetCurrentUserInfo(c *gin.Context) (userInfo models.User, exist bool) {
|
||||
if value, exist := c.Get("userInfo"); exist {
|
||||
if v, ok := value.(models.User); ok {
|
||||
return v, exist
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 验证器验证
|
||||
func VerificationCheck(verificationId, vCode string) (errCode int, verificationIdRes string) {
|
||||
|
||||
// 需要进一步验证并返回验证信息
|
||||
if verificationId == "" || vCode == "" {
|
||||
verificationIdRes = cmn.BuildRandCode(16, cmn.RAND_CODE_MODE1)
|
||||
errCode = apiReturn.ERROR_CODE_VERIFICATION_MUST
|
||||
return
|
||||
}
|
||||
|
||||
// 验证码错误
|
||||
if !captcha.CaptchaVerifyHandle(verificationId, vCode) {
|
||||
errCode = apiReturn.ERROR_CODE_VERIFICATION_FAIL
|
||||
return
|
||||
}
|
||||
errCode = apiReturn.ERROR_CODE_SUCCESS
|
||||
return
|
||||
}
|
20
service/api/api_v1/middleware/AdminInterceptor.go
Normal file
@ -0,0 +1,20 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
// "calendar-note-gin/api/v1/common/apiReturn"
|
||||
// . "calendar-note-gin/api/v1/common/base"
|
||||
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AdminInterceptor(c *gin.Context) {
|
||||
currentUser, _ := base.GetCurrentUserInfo(c)
|
||||
if currentUser.Role != 1 {
|
||||
apiReturn.ErrorNoAccess(c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
78
service/api/api_v1/middleware/LoginInterceptor.go
Normal file
@ -0,0 +1,78 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/global"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func LoginInterceptor(c *gin.Context) {
|
||||
|
||||
// 继续执行后续的操作,再回来
|
||||
// c.Next()
|
||||
|
||||
// 获得token
|
||||
cToken := c.GetHeader("token")
|
||||
|
||||
// 没有token信息视为未登录
|
||||
if cToken == "" {
|
||||
apiReturn.ErrorCode(c, 1000, global.Lang.Get("login.err_not_login"), nil)
|
||||
c.Abort() // 终止执行后续的操作,一般配合return使用
|
||||
return
|
||||
}
|
||||
|
||||
token := ""
|
||||
{
|
||||
var ok bool
|
||||
token, ok = global.CUserToken.Get(cToken)
|
||||
// 可能已经安全退出或者很久没有使用已过期
|
||||
if !ok || token == "" {
|
||||
apiReturn.ErrorCode(c, 1001, global.Lang.Get("login.err_not_login"), nil)
|
||||
c.Abort() // 终止执行后续的操作,一般配合return使用
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 直接返回缓存的用户信息
|
||||
if userInfo, success := global.UserToken.Get(token); success {
|
||||
c.Set("userInfo", userInfo)
|
||||
return
|
||||
}
|
||||
|
||||
global.Logger.Debug("准备查询数据库的用户资料", token)
|
||||
|
||||
mUser := models.User{}
|
||||
// 去库中查询是否存在该用户;否则返回错误
|
||||
if info, err := mUser.GetUserInfoByToken(token); err != nil || info.Token == "" || info.ID == 0 {
|
||||
apiReturn.ErrorCode(c, 1001, global.Lang.Get("login.err_token_expire"), nil)
|
||||
c.Abort()
|
||||
return
|
||||
} else {
|
||||
// 通过 设置当前用户信息
|
||||
global.UserToken.SetDefault(info.Token, info)
|
||||
global.CUserToken.SetDefault(cToken, token)
|
||||
c.Set("userInfo", info)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 不验证缓存直接验证库省去没有缓存每次都要手动登录的问题
|
||||
func LoginInterceptorDev(c *gin.Context) {
|
||||
|
||||
// 获得token
|
||||
token := c.GetHeader("token")
|
||||
mUser := models.User{}
|
||||
|
||||
// 去库中查询是否存在该用户;否则返回错误
|
||||
if info, err := mUser.GetUserInfoByToken(token); err != nil || info.ID == 0 {
|
||||
apiReturn.ErrorCode(c, 1001, global.Lang.Get("login.err_token_expire"), nil)
|
||||
c.Abort()
|
||||
return
|
||||
} else {
|
||||
// 通过
|
||||
// 设置当前用户信息
|
||||
c.Set("userInfo", info)
|
||||
}
|
||||
}
|
5
service/api/api_v1/openness/enter.go
Normal file
@ -0,0 +1,5 @@
|
||||
package openness
|
||||
|
||||
type ApiPpenness struct {
|
||||
Openness Openness
|
||||
}
|
44
service/api/api_v1/openness/openness.go
Normal file
@ -0,0 +1,44 @@
|
||||
package openness
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn/systemSetting"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Openness struct {
|
||||
}
|
||||
|
||||
func (a *Openness) LoginConfig(c *gin.Context) {
|
||||
cfg := systemSetting.ApplicationSetting{}
|
||||
if err := global.SystemSetting.GetValueByInterface(systemSetting.SYSTEM_APPLICATION, &cfg); err != nil {
|
||||
apiReturn.Error(c, "配置查询失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
apiReturn.SuccessData(c, gin.H{
|
||||
"loginCaptcha": cfg.LoginCaptcha,
|
||||
"register": cfg.Register,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Openness) GetDisclaimer(c *gin.Context) {
|
||||
if content, err := global.SystemSetting.GetValueString(systemSetting.DISCLAIMER); err != nil {
|
||||
global.SystemSetting.Set(systemSetting.DISCLAIMER, "")
|
||||
apiReturn.SuccessData(c, "")
|
||||
return
|
||||
} else {
|
||||
apiReturn.SuccessData(c, content)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Openness) GetAboutDescription(c *gin.Context) {
|
||||
if content, err := global.SystemSetting.GetValueString(systemSetting.WEB_ABOUT_DESCRIPTION); err != nil {
|
||||
global.SystemSetting.Set(systemSetting.WEB_ABOUT_DESCRIPTION, "")
|
||||
apiReturn.SuccessData(c, "")
|
||||
return
|
||||
} else {
|
||||
apiReturn.SuccessData(c, content)
|
||||
}
|
||||
}
|
7
service/api/api_v1/panel/A_ENTER.go
Normal file
@ -0,0 +1,7 @@
|
||||
package panel
|
||||
|
||||
type ApiPanel struct {
|
||||
ItemIcon ItemIcon
|
||||
UserConfig UserConfig
|
||||
UsersApi UsersApi
|
||||
}
|
100
service/api/api_v1/panel/itemIcon.go
Normal file
@ -0,0 +1,100 @@
|
||||
package panel
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sun-panel/api/api_v1/common/apiData/commonApiStructs"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
type ItemIcon struct {
|
||||
}
|
||||
|
||||
func (a *ItemIcon) Edit(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
req := models.ItemIcon{}
|
||||
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
req.UserId = userInfo.ID
|
||||
req.GroupId = 1
|
||||
req.Sort = 1
|
||||
|
||||
// json转字符串
|
||||
if j, err := json.Marshal(req.Icon); err == nil {
|
||||
req.IconJson = string(j)
|
||||
}
|
||||
|
||||
if req.ID != 0 {
|
||||
// 修改
|
||||
global.Db.Model(&models.ItemIcon{}).
|
||||
Select("IconJson", "Icon", "Title", "Url", "LanUrl", "Description", "OpenMethod", "Sort", "GroupId", "UserId").
|
||||
Where("id=?", req.ID).Updates(&req)
|
||||
} else {
|
||||
// 创建
|
||||
global.Db.Create(&req)
|
||||
}
|
||||
|
||||
apiReturn.SuccessData(c, req)
|
||||
}
|
||||
|
||||
// // 获取详情
|
||||
// func (a *ItemIcon) GetInfo(c *gin.Context) {
|
||||
// req := systemApiStructs.AiDrawGetInfoReq{}
|
||||
|
||||
// if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
// apiReturn.ErrorParamFomat(c, err.Error())
|
||||
// return
|
||||
// }
|
||||
|
||||
// userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
|
||||
// aiDraw := models.AiDraw{}
|
||||
// aiDraw.ID = req.ID
|
||||
// if err := aiDraw.GetInfo(global.Db); err != nil {
|
||||
// if err == gorm.ErrRecordNotFound {
|
||||
// apiReturn.Error(c, "不存在记录")
|
||||
// return
|
||||
// }
|
||||
// apiReturn.ErrorDatabase(c, err.Error())
|
||||
// return
|
||||
// }
|
||||
|
||||
// if userInfo.ID != aiDraw.UserID {
|
||||
// apiReturn.ErrorNoAccess(c)
|
||||
// return
|
||||
// }
|
||||
|
||||
// apiReturn.SuccessData(c, aiDraw)
|
||||
// }
|
||||
|
||||
func (a *ItemIcon) GetListByGroupId(c *gin.Context) {
|
||||
req := commonApiStructs.RequestPage{}
|
||||
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
itemIcons := []models.ItemIcon{}
|
||||
|
||||
if err := global.Db.Order("sort ,created_at DESC").Where("user_id=?", userInfo.ID).Find(&itemIcons, "group_id = ? AND user_id=?", 1, userInfo.ID).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range itemIcons {
|
||||
json.Unmarshal([]byte(v.IconJson), &itemIcons[k].Icon)
|
||||
}
|
||||
|
||||
apiReturn.SuccessListData(c, itemIcons, 0)
|
||||
}
|
87
service/api/api_v1/panel/userConfig.go
Normal file
@ -0,0 +1,87 @@
|
||||
package panel
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserConfig struct {
|
||||
}
|
||||
|
||||
func (a *UserConfig) Get(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
cfg := models.UserConfig{}
|
||||
if err := global.Db.First(&cfg, "user_id=?", userInfo.ID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
apiReturn.ErrorDataNotFound(c)
|
||||
return
|
||||
} else {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 处理字段
|
||||
if err := json.Unmarshal([]byte(cfg.PanelJson), &cfg.Panel); err != nil {
|
||||
cfg.Panel = nil
|
||||
}
|
||||
if err := json.Unmarshal([]byte(cfg.SearchEngineJson), &cfg.SearchEngine); err != nil {
|
||||
cfg.SearchEngine = nil
|
||||
}
|
||||
apiReturn.SuccessData(c, cfg)
|
||||
|
||||
}
|
||||
|
||||
func (a *UserConfig) Set(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
req := models.UserConfig{}
|
||||
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 处理字段
|
||||
if jb, err := json.Marshal(req.Panel); err != nil {
|
||||
req.PanelJson = "{}"
|
||||
} else {
|
||||
req.PanelJson = string(jb)
|
||||
}
|
||||
|
||||
if jb, err := json.Marshal(req.SearchEngine); err != nil {
|
||||
req.SearchEngineJson = "{}"
|
||||
} else {
|
||||
req.SearchEngineJson = string(jb)
|
||||
}
|
||||
|
||||
// 保存操作
|
||||
if err := global.Db.First(&models.UserConfig{}, "user_id=?", userInfo.ID).Error; err != nil {
|
||||
req.UserId = userInfo.ID
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// 新增
|
||||
if err := global.Db.Create(&req).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 报错
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 修改
|
||||
if err := global.Db.Where("user_id=?", userInfo.ID).Updates(&req).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
apiReturn.Success(c)
|
||||
}
|
169
service/api/api_v1/panel/users.go
Normal file
@ -0,0 +1,169 @@
|
||||
package panel
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
// 此API 临时使用,后期带有管理功能,将废除!!!
|
||||
type UsersApi struct {
|
||||
}
|
||||
|
||||
func (a UsersApi) Create(c *gin.Context) {
|
||||
param := models.User{}
|
||||
if err := c.ShouldBindBodyWith(¶m, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if errMsg, err := base.ValidateInputStruct(param); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
mUser := models.User{
|
||||
Username: param.Username,
|
||||
Password: cmn.PasswordEncryption(param.Password),
|
||||
Name: param.Username,
|
||||
HeadImage: param.HeadImage,
|
||||
Status: 1,
|
||||
Role: 1, // 固定管理员
|
||||
Mail: param.Username,
|
||||
}
|
||||
|
||||
// 验证账号是否存在
|
||||
if _, err := mUser.CheckUsernameExist(param.Username); err != nil {
|
||||
apiReturn.Error(c, global.Lang.Get("register.mail_exist"))
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, err := mUser.CreateOne()
|
||||
|
||||
if err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
apiReturn.SuccessData(c, gin.H{"userId": userInfo.ID})
|
||||
}
|
||||
|
||||
func (a UsersApi) Deletes(c *gin.Context) {
|
||||
type UserIds struct {
|
||||
UserIds []uint
|
||||
}
|
||||
param := UserIds{}
|
||||
if err := c.ShouldBindBodyWith(¶m, binding.JSON); err != nil {
|
||||
apiReturn.Error(c, global.Lang.GetAndInsert("common.api_error_param_format", "[", err.Error(), "]"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if err := global.Db.Delete(&models.User{}, ¶m.UserIds).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
||||
func (a UsersApi) Update(c *gin.Context) {
|
||||
param := models.User{}
|
||||
if err := c.ShouldBindBodyWith(¶m, binding.JSON); err != nil {
|
||||
apiReturn.Error(c, global.Lang.GetAndInsert("common.api_error_param_format", "[", err.Error(), "]"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if param.Password == "" {
|
||||
param.Password = "-" // 修改不允许修改密码,为了验证通过
|
||||
}
|
||||
|
||||
param.Mail = param.Username // 密码邮箱同时修改
|
||||
if errMsg, err := base.ValidateInputStruct(param); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
allowField := []string{"Username", "Name", "Mail", "Token"}
|
||||
|
||||
// 密码不为默认“-”空,修改密码
|
||||
if param.Password != "-" {
|
||||
param.Password = cmn.PasswordEncryption(param.Password)
|
||||
allowField = append(allowField, "Password")
|
||||
}
|
||||
mUser := models.User{}
|
||||
|
||||
userInfo := models.User{}
|
||||
// 验证账号是否存在
|
||||
if user, err := mUser.CheckUsernameExist(param.Username); err != nil {
|
||||
userInfo = user
|
||||
if user.ID != param.ID {
|
||||
apiReturn.Error(c, global.Lang.Get("register.mail_exist"))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
userInfo = user
|
||||
}
|
||||
|
||||
param.Token = "" // 修改资料就重置token
|
||||
if err := global.Db.Select(allowField).Where("id=?", param.ID).Updates(¶m).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
// global.Logger.Debug("修改资料清空token", userInfo.Token)
|
||||
global.UserToken.Delete(userInfo.Token) // 更新用户信息
|
||||
// 返回token等基本信息
|
||||
apiReturn.SuccessData(c, param)
|
||||
}
|
||||
|
||||
func (a UsersApi) GetList(c *gin.Context) {
|
||||
|
||||
type ParamsStruct struct {
|
||||
models.User
|
||||
Limit int
|
||||
Page int
|
||||
Keyword string `json:"keyword"`
|
||||
}
|
||||
|
||||
param := ParamsStruct{}
|
||||
if err := c.ShouldBindBodyWith(¶m, binding.JSON); err != nil {
|
||||
apiReturn.Error(c, global.Lang.GetAndInsert("common.api_error_param_format", "[", err.Error(), "]"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
list []models.User
|
||||
count int64
|
||||
)
|
||||
db := global.Db
|
||||
|
||||
// 查询条件
|
||||
if param.Keyword != "" {
|
||||
db = db.Where("name LIKE ? OR username LIKE ?", "%"+param.Keyword+"%", "%"+param.Keyword+"%")
|
||||
}
|
||||
|
||||
if err := db.Omit("Password").Limit(param.Limit).Offset((param.Page - 1) * param.Limit).Find(&list).Limit(-1).Offset(-1).Count(&count).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// resMap := []map[string]interface{}{}
|
||||
// for _, v := range list {
|
||||
// resMap = append(resMap, map[string]interface{}{
|
||||
// "userId": v.ID,
|
||||
// "name": v.Name,
|
||||
// "headImage": v.HeadImage,
|
||||
// "status": v.Status,
|
||||
// "role": v.Role,
|
||||
// "username": v.Username,
|
||||
// })
|
||||
// }
|
||||
|
||||
apiReturn.SuccessListData(c, list, count)
|
||||
}
|
11
service/api/api_v1/system/A_ENTER.go
Normal file
@ -0,0 +1,11 @@
|
||||
package system
|
||||
|
||||
type ApiSystem struct {
|
||||
About About
|
||||
LoginApi LoginApi
|
||||
UserApi UserApi
|
||||
FileApi FileApi
|
||||
CaptchaApi CaptchaApi
|
||||
RegisterApi RegisterApi
|
||||
NoticeApi NoticeApi
|
||||
}
|
19
service/api/api_v1/system/about.go
Normal file
@ -0,0 +1,19 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/lib/cmn"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type About struct {
|
||||
}
|
||||
|
||||
func (a *About) Get(c *gin.Context) {
|
||||
version := cmn.GetSysVersionInfo()
|
||||
apiReturn.SuccessData(c, gin.H{
|
||||
"versionName": version.Version,
|
||||
"versionCode": version.Version_code,
|
||||
})
|
||||
}
|
63
service/api/api_v1/system/captcha.go
Normal file
@ -0,0 +1,63 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strconv"
|
||||
"sun-panel/lib/captcha"
|
||||
"sun-panel/lib/cmn"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CaptchaApi struct {
|
||||
ErrMsg string // 错误信息
|
||||
}
|
||||
|
||||
// 获取图像
|
||||
func (c *CaptchaApi) GetImage(ctx *gin.Context) {
|
||||
key := cmn.BuildRandCode(16, cmn.RAND_CODE_MODE2)
|
||||
width, _ := strconv.Atoi(ctx.Param("width"))
|
||||
height, _ := strconv.Atoi(ctx.Param("height"))
|
||||
if width == 0 || width > 500 {
|
||||
width = 120
|
||||
}
|
||||
if height == 0 || height > 500 {
|
||||
height = 44
|
||||
}
|
||||
// 设置网页验证码的cookie
|
||||
ctx.SetCookie("CaptchaId", key, 3600, "/", "", false, false)
|
||||
base64Str := captcha.GenerateCaptchaHandler(key, width, height)
|
||||
_ = base64Str
|
||||
// base64 字符串一般会包含头部 data:image/xxx;base64, 需要去除
|
||||
baseImg, _ := base64.StdEncoding.DecodeString(base64Str[22:])
|
||||
_, _ = ctx.Writer.WriteString(string(baseImg))
|
||||
}
|
||||
|
||||
// 获取图像根据验证器id,id从地址栏获取
|
||||
func (c *CaptchaApi) GetImageByCaptchaId(ctx *gin.Context) {
|
||||
// key := cmn.BuildRandCode(16, cmn.RAND_CODE_MODE2)
|
||||
width, _ := strconv.Atoi(ctx.Param("width"))
|
||||
height, _ := strconv.Atoi(ctx.Param("height"))
|
||||
captchaId := ctx.Param("captchaId")
|
||||
if width == 0 || width > 500 {
|
||||
width = 120
|
||||
}
|
||||
if height == 0 || height > 500 {
|
||||
height = 44
|
||||
}
|
||||
// 设置网页验证码的cookie
|
||||
base64Str := captcha.GenerateCaptchaHandler(captchaId, width, height)
|
||||
_ = base64Str
|
||||
// base64 字符串一般会包含头部 data:image/xxx;base64, 需要去除
|
||||
baseImg, _ := base64.StdEncoding.DecodeString(base64Str[22:])
|
||||
_, _ = ctx.Writer.WriteString(string(baseImg))
|
||||
}
|
||||
|
||||
func (c *CaptchaApi) CheckVCode(id, vcode string) {
|
||||
// Captcha.Store = base64Captcha.DefaultMemStore
|
||||
// if store.Verify(id, vcode, true) {
|
||||
// body = map[string]interface{}{"code": 1001, "msg": "ok"}
|
||||
// }
|
||||
// w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
// json.NewEncoder(w).Encode(body)
|
||||
}
|
87
service/api/api_v1/system/file.go
Normal file
@ -0,0 +1,87 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/models"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type FileApi struct{}
|
||||
|
||||
func (a *FileApi) UploadImg(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
configUpload := global.Config.GetValueString("base", "source_path")
|
||||
f, err := c.FormFile("imgfile")
|
||||
if err != nil {
|
||||
apiReturn.Error(c, "上传失败")
|
||||
return
|
||||
} else {
|
||||
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||
if fileExt != ".png" && fileExt != ".jpg" && fileExt != ".gif" && fileExt != ".jpeg" && fileExt != ".webp" && fileExt != ".svg" {
|
||||
apiReturn.Error(c, "上传失败!只允许png,jpg,gif,jpeg,svg文件")
|
||||
return
|
||||
}
|
||||
fileName := cmn.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||
fildDir := fmt.Sprintf("%s/%d/%d/%d/", configUpload, time.Now().Year(), time.Now().Month(), time.Now().Day())
|
||||
isExist, _ := cmn.PathExists(fildDir)
|
||||
if !isExist {
|
||||
os.MkdirAll(fildDir, os.ModePerm)
|
||||
}
|
||||
filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||
c.SaveUploadedFile(f, filepath)
|
||||
|
||||
// 像数据库添加记录
|
||||
mFile := models.File{}
|
||||
mFile.AddFile(userInfo.ID, f.Filename, fileExt, filepath)
|
||||
apiReturn.SuccessData(c, gin.H{
|
||||
"imageUrl": filepath[1:],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (a *FileApi) UploadFiles(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
configUpload := global.Config.GetValueString("base", "source_path")
|
||||
|
||||
form, err := c.MultipartForm()
|
||||
if err != nil {
|
||||
apiReturn.Error(c, "上传失败")
|
||||
return
|
||||
}
|
||||
files := form.File["files[]"]
|
||||
errFiles := []string{}
|
||||
succMap := map[string]string{}
|
||||
for _, f := range files {
|
||||
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||
fileName := cmn.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||
fildDir := fmt.Sprintf("%s/%d/%d/%d/", configUpload, time.Now().Year(), time.Now().Month(), time.Now().Day())
|
||||
isExist, _ := cmn.PathExists(fildDir)
|
||||
if !isExist {
|
||||
os.MkdirAll(fildDir, os.ModePerm)
|
||||
}
|
||||
filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||
if c.SaveUploadedFile(f, filepath) != nil {
|
||||
errFiles = append(errFiles, f.Filename)
|
||||
} else {
|
||||
// 成功
|
||||
// 像数据库添加记录
|
||||
mFile := models.File{}
|
||||
mFile.AddFile(userInfo.ID, f.Filename, fileExt, filepath)
|
||||
succMap[f.Filename] = filepath[1:]
|
||||
}
|
||||
}
|
||||
|
||||
apiReturn.SuccessData(c, gin.H{
|
||||
"succMap": succMap,
|
||||
"errFiles": errFiles,
|
||||
})
|
||||
}
|
215
service/api/api_v1/system/login.go
Normal file
@ -0,0 +1,215 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sun-panel/api/api_v1/common/apiData/commonApiStructs"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/captcha"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/lib/cmn/systemSetting"
|
||||
"sun-panel/lib/mail"
|
||||
"sun-panel/models"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LoginApi struct {
|
||||
}
|
||||
|
||||
// 登录输入验证
|
||||
type LoginLoginVerify struct {
|
||||
Username string `json:"username" validate:"required,min=5"`
|
||||
Password string `json:"password" validate:"required,min=5,max=20"`
|
||||
VCode string `json:"vcode" validate:"max=6"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// @Summary 登录账号
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param LoginLoginVerify body LoginLoginVerify true "登陆验证信息"
|
||||
// @Tags user
|
||||
// @Router /login [post]
|
||||
func (l LoginApi) Login(c *gin.Context) {
|
||||
param := LoginLoginVerify{}
|
||||
if err := c.ShouldBindJSON(¶m); err != nil {
|
||||
apiReturn.Error(c, global.Lang.Get("common.api_error_param_format"))
|
||||
return
|
||||
}
|
||||
|
||||
if errMsg, err := base.ValidateInputStruct(param); err != nil {
|
||||
apiReturn.Error(c, errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
settings := systemSetting.ApplicationSetting{}
|
||||
global.SystemSetting.GetValueByInterface("system_application", &settings)
|
||||
|
||||
// 验证验证码
|
||||
if settings.Login.LoginCaptcha {
|
||||
var captchaId string
|
||||
var err error
|
||||
|
||||
// 获取captchaId
|
||||
if captchaId, err = captcha.CaptchaGetIdByCookieHeader(c, "CaptchaId"); err != nil {
|
||||
apiReturn.Error(c, global.Lang.Get("login.err_captcha_check_fail"))
|
||||
return
|
||||
}
|
||||
|
||||
// 验证码错误
|
||||
if !captcha.CaptchaVerifyHandle(captchaId, param.VCode) {
|
||||
apiReturn.Error(c, global.Lang.Get("captcha.api_captcha_fail"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mUser := models.User{}
|
||||
var (
|
||||
err error
|
||||
info models.User
|
||||
)
|
||||
bToken := ""
|
||||
if info, err = mUser.GetUserInfoByUsernameAndPassword(param.Username, cmn.PasswordEncryption(param.Password)); err != nil {
|
||||
// 未找到记录 账号或密码错误
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
apiReturn.Error(c, global.Lang.Get("login.err_username_password"))
|
||||
return
|
||||
} else {
|
||||
// 未知错误
|
||||
apiReturn.Error(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 停用或未激活
|
||||
if info.Status != 1 {
|
||||
apiReturn.Error(c, global.Lang.Get("login.err_username_deactivation"))
|
||||
return
|
||||
}
|
||||
|
||||
bToken = info.Token
|
||||
if info.Token == "" {
|
||||
// 生成token
|
||||
buildTokenOver := false
|
||||
for !buildTokenOver {
|
||||
bToken = cmn.BuildRandCode(32, cmn.RAND_CODE_MODE2)
|
||||
if _, err := mUser.GetUserInfoByToken(bToken); err != nil {
|
||||
// 保存token
|
||||
mUser.UpdateUserInfoByUserId(info.ID, map[string]interface{}{
|
||||
"token": bToken,
|
||||
})
|
||||
buildTokenOver = true
|
||||
}
|
||||
}
|
||||
info.Token = bToken
|
||||
}
|
||||
|
||||
// global.UserToken.SetDefault(bToken, info)
|
||||
cToken := uuid.NewString() + "-" + cmn.Md5(cmn.Md5("userId"+strconv.Itoa(int(info.ID))))
|
||||
global.CUserToken.SetDefault(cToken, bToken)
|
||||
global.Logger.Debug("token:", cToken, "|", bToken)
|
||||
global.Logger.Debug(global.CUserToken.Get(cToken))
|
||||
|
||||
// 设置当前用户信息
|
||||
c.Set("userInfo", info)
|
||||
info.Token = cToken // 重要 采用cToken,隐藏真实token
|
||||
apiReturn.SuccessData(c, info)
|
||||
}
|
||||
|
||||
// 安全退出
|
||||
func (l *LoginApi) Logout(c *gin.Context) {
|
||||
// userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
cToken := c.GetHeader("token")
|
||||
global.CUserToken.Delete(cToken)
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
||||
// 获取重置密码的验证码
|
||||
func (l *LoginApi) SendResetPasswordVCode(c *gin.Context) {
|
||||
type ResstRequest struct {
|
||||
LoginLoginVerify
|
||||
Verification commonApiStructs.VerificationRequest `json:"verification"`
|
||||
}
|
||||
req := ResstRequest{}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
apiReturn.Error(c, global.Lang.Get("common.api_error_param_format"))
|
||||
return
|
||||
}
|
||||
|
||||
// 验证码验证
|
||||
{
|
||||
errCode, verifcationId := base.VerificationCheck(req.Verification.CodeID, req.Verification.VCode)
|
||||
if errCode != apiReturn.ERROR_CODE_SUCCESS {
|
||||
apiReturn.ErrorVerification(c, errCode, verifcationId)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
emailVCode := cmn.BuildRandCode(6, cmn.RAND_CODE_MODE2)
|
||||
global.VerifyCodeCachePool.Set(req.Email, emailVCode, 10*time.Minute)
|
||||
|
||||
userCheck := &models.User{Mail: req.Email}
|
||||
userInfo := userCheck.GetUserInfoByMail()
|
||||
if userInfo == nil {
|
||||
apiReturn.Error(c, "账号不存在")
|
||||
return
|
||||
}
|
||||
emailInfoConfig := systemSetting.Email{}
|
||||
global.SystemSetting.GetValueByInterface("system_email", &emailInfoConfig)
|
||||
emailInfo := mail.EmailInfo{
|
||||
Username: emailInfoConfig.Mail,
|
||||
Password: emailInfoConfig.Password,
|
||||
Host: emailInfoConfig.Host,
|
||||
Port: emailInfoConfig.Port,
|
||||
}
|
||||
if err := mail.SendResetPasswordVCode(mail.NewEmailer(emailInfo), req.Email, emailVCode); err != nil {
|
||||
apiReturn.Error(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
apiReturn.Success(c)
|
||||
|
||||
}
|
||||
|
||||
// 使用邮箱验证码重置密码
|
||||
func (l *LoginApi) ResetPasswordByVCode(c *gin.Context) {
|
||||
req := registerInfo{}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
apiReturn.Error(c, global.Lang.Get("common.api_error_param_format"))
|
||||
return
|
||||
}
|
||||
|
||||
userCheck := &models.User{Mail: req.Email}
|
||||
userInfo := userCheck.GetUserInfoByMail()
|
||||
if userInfo == nil {
|
||||
apiReturn.Error(c, "账号不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// 校验验证码
|
||||
{
|
||||
if emailVCode, ok := global.VerifyCodeCachePool.Get(req.Email); !ok || req.EmailVCode != emailVCode {
|
||||
apiReturn.Error(c, global.Lang.Get("common.captcha_code_error"))
|
||||
return
|
||||
}
|
||||
global.VerifyCodeCachePool.Delete(req.Email)
|
||||
}
|
||||
|
||||
updateData := map[string]interface{}{
|
||||
"password": cmn.PasswordEncryption(req.Password),
|
||||
"token": "",
|
||||
}
|
||||
global.UserToken.Delete(userInfo.Token) // 更新用户信息
|
||||
if err := userInfo.UpdateUserInfoByUserId(userInfo.ID, updateData); err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
apiReturn.Success(c)
|
||||
|
||||
}
|
28
service/api/api_v1/system/notice.go
Normal file
@ -0,0 +1,28 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiData/systemApiStructs"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/global"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
type NoticeApi struct {
|
||||
}
|
||||
|
||||
func (a *NoticeApi) GetListByDisplayType(c *gin.Context) {
|
||||
req := systemApiStructs.NoticeGetListByDisplayTypeReq{}
|
||||
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
noticeList := []models.Notice{}
|
||||
if err := global.Db.Find(¬iceList, "display_type in ?", req.DisplayType).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
apiReturn.SuccessListData(c, noticeList, 0)
|
||||
}
|
36
service/api/api_v1/system/rateLimit/rateLimit.go
Normal file
@ -0,0 +1,36 @@
|
||||
package rateLimit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sun-panel/global"
|
||||
)
|
||||
|
||||
const (
|
||||
ERROR_RATE_EXCEED_MINUTE = "minute exceed" // 分钟速率超出限制
|
||||
ERROR_RATE_EXCEED_HOUR = "minute hour" // 小时速率超出限制
|
||||
)
|
||||
|
||||
// 获取用户套餐的速率 此处正常根据用户套餐设定获取-暂时写死
|
||||
func GetUserPackageRate(userId uint) (minuteRate, hourRate int) {
|
||||
return 10, 200
|
||||
}
|
||||
|
||||
func CheckRateLimit(userId uint) error {
|
||||
minuteRate, hourRate := GetUserPackageRate(userId)
|
||||
if minuteRate != 0 && minuteRate <= global.RateLimit.MinuteGet(userId) {
|
||||
return errors.New(ERROR_RATE_EXCEED_MINUTE)
|
||||
}
|
||||
|
||||
if hourRate != 0 && hourRate <= global.RateLimit.HourGet(userId) {
|
||||
return errors.New(ERROR_RATE_EXCEED_HOUR)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 速率+1次 同时增加小时和分钟的次数
|
||||
func AddOnceRate(userId uint) error {
|
||||
global.RateLimit.MinuteAddOnce(userId)
|
||||
global.RateLimit.HourAddOnce(userId)
|
||||
return nil
|
||||
}
|
183
service/api/api_v1/system/register.go
Normal file
@ -0,0 +1,183 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sun-panel/api/api_v1/common/apiData/commonApiStructs"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/lib/cmn/systemSetting"
|
||||
"sun-panel/lib/mail"
|
||||
"sun-panel/models"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type registerInfo struct {
|
||||
Email string `json:"email"`
|
||||
UserName string `json:"userName"`
|
||||
Password string `json:"password"`
|
||||
Vcode string `json:"vcode"`
|
||||
EmailVCode string `json:"emailVCode"`
|
||||
VCode string `json:"vCode"`
|
||||
Verification commonApiStructs.VerificationRequest `json:"verification"`
|
||||
ReferralCode string `json:"referralCode"`
|
||||
}
|
||||
|
||||
const EmailCodeCapacity = 1000
|
||||
|
||||
type RegisterApi struct{}
|
||||
|
||||
// 获取注册验证码
|
||||
func (l RegisterApi) SendRegisterVcode(c *gin.Context) {
|
||||
req := registerInfo{}
|
||||
err := c.ShouldBindJSON(&req)
|
||||
req.Email = req.UserName
|
||||
if err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
errMsg, err := base.ValidateInputStruct(req)
|
||||
if err != nil {
|
||||
apiReturn.Error(c, errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证码验证
|
||||
{
|
||||
errCode, verifcationId := base.VerificationCheck(req.Verification.CodeID, req.Verification.VCode)
|
||||
if errCode != apiReturn.ERROR_CODE_SUCCESS {
|
||||
apiReturn.ErrorVerification(c, errCode, verifcationId)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 验证是否开启注册和后缀格式是否正确
|
||||
{
|
||||
systemSettingInfo := systemSetting.ApplicationSetting{}
|
||||
if err := global.SystemSetting.GetValueByInterface("system_application", &systemSettingInfo); err != nil || !systemSettingInfo.Register.OpenRegister {
|
||||
apiReturn.Error(c, global.Lang.Get("register.unopened_register"))
|
||||
return
|
||||
}
|
||||
|
||||
if systemSettingInfo.Register.EmailSuffix != "" && !cmn.VerifyFormat("^.*"+systemSettingInfo.Register.EmailSuffix+"$", req.Email) {
|
||||
apiReturn.Error(c, global.Lang.GetWithFields("register.emailSuffix_error", map[string]string{"EmailSuffix": systemSettingInfo.Register.EmailSuffix}))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 验证邮箱是否被注册
|
||||
{
|
||||
userCheck := &models.User{Mail: req.UserName}
|
||||
if _, err := userCheck.GetUserInfoByUsername(req.UserName); err == nil && err != gorm.ErrRecordNotFound {
|
||||
apiReturn.Error(c, global.Lang.Get("register.mail_exist"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
emailCode := generateEmailCode()
|
||||
count, err := global.VerifyCodeCachePool.ItemCount()
|
||||
if err != nil || count >= EmailCodeCapacity {
|
||||
global.VerifyCodeCachePool.Flush()
|
||||
}
|
||||
global.VerifyCodeCachePool.Set(req.Email, emailCode, 0)
|
||||
emailInfoConfig := systemSetting.Email{}
|
||||
global.SystemSetting.GetValueByInterface("system_email", &emailInfoConfig)
|
||||
emailInfo := mail.EmailInfo{
|
||||
Username: emailInfoConfig.Mail,
|
||||
Password: emailInfoConfig.Password,
|
||||
Host: emailInfoConfig.Host,
|
||||
Port: emailInfoConfig.Port,
|
||||
}
|
||||
err = mail.SendRegisterEmail(mail.NewEmailer(emailInfo), req.Email, emailCode)
|
||||
if err != nil {
|
||||
apiReturn.Error(c, global.Lang.Get("mail.send_mail_fail"))
|
||||
global.Logger.Errorf("[register] fail to send email to%s", req.UserName)
|
||||
return
|
||||
}
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
||||
// 注册提交(开始注册)
|
||||
func (l *RegisterApi) Commit(c *gin.Context) {
|
||||
req := registerInfo{}
|
||||
err := c.ShouldBindJSON(&req)
|
||||
req.Email = req.UserName
|
||||
if err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
errMsg, err := base.ValidateInputStruct(req)
|
||||
if err != nil {
|
||||
apiReturn.Error(c, errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证是否开启注册和后缀格式是否正确
|
||||
{
|
||||
systemSettingInfo := systemSetting.ApplicationSetting{}
|
||||
if err := global.SystemSetting.GetValueByInterface("system_application", &systemSettingInfo); err != nil || !systemSettingInfo.Register.OpenRegister {
|
||||
apiReturn.Error(c, global.Lang.Get("register.unopened_register"))
|
||||
return
|
||||
}
|
||||
|
||||
if systemSettingInfo.Register.EmailSuffix != "" && !cmn.VerifyFormat("^.*"+systemSettingInfo.Register.EmailSuffix+"$", req.Email) {
|
||||
apiReturn.Error(c, global.Lang.GetWithFields("register.emailSuffix_error", map[string]string{"EmailSuffix": systemSettingInfo.Register.EmailSuffix}))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 验证邮箱是否被注册
|
||||
{
|
||||
userCheck := &models.User{Mail: req.UserName}
|
||||
if _, err := userCheck.GetUserInfoByUsername(req.UserName); err == nil && err != gorm.ErrRecordNotFound {
|
||||
apiReturn.Error(c, global.Lang.Get("register.mail_exist"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 验证码验证
|
||||
{
|
||||
vCode, ok := global.VerifyCodeCachePool.Get(req.Email)
|
||||
if !ok {
|
||||
apiReturn.Error(c, global.Lang.Get("common.captcha_code_error"))
|
||||
//验证码不存在
|
||||
return
|
||||
}
|
||||
if vCode != req.EmailVCode {
|
||||
apiReturn.Error(c, global.Lang.Get("common.captcha_code_error"))
|
||||
return
|
||||
//验证码有误
|
||||
}
|
||||
}
|
||||
|
||||
// 自动生成用户昵称
|
||||
name := "用户" + cmn.BuildRandCode(4, cmn.RAND_CODE_MODE3)
|
||||
|
||||
//验证通过,注册
|
||||
user := &models.User{
|
||||
Mail: req.UserName,
|
||||
Name: name,
|
||||
Username: req.UserName,
|
||||
Password: cmn.PasswordEncryption(req.Password),
|
||||
Status: 1,
|
||||
Role: 2,
|
||||
}
|
||||
_, err = user.CreateOne()
|
||||
if err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
//删除旧的验证码
|
||||
global.VerifyCodeCachePool.Delete(req.Email)
|
||||
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
||||
func generateEmailCode() string {
|
||||
return fmt.Sprintf("%06v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000000))
|
||||
}
|
136
service/api/api_v1/system/user.go
Normal file
@ -0,0 +1,136 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"sun-panel/api/api_v1/common/apiData/systemApiStructs"
|
||||
"sun-panel/api/api_v1/common/apiReturn"
|
||||
"sun-panel/api/api_v1/common/base"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
type UserApi struct{}
|
||||
|
||||
func (a *UserApi) GetInfo(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
apiReturn.SuccessData(c, gin.H{
|
||||
"userId": userInfo.ID,
|
||||
"id": userInfo.ID,
|
||||
"headImage": userInfo.HeadImage,
|
||||
"name": userInfo.Name,
|
||||
"role": userInfo.Role,
|
||||
// "token": userInfo.Token,
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// 修改资料
|
||||
func (a *UserApi) UpdateInfo(c *gin.Context) {
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
type UpdateUserInfoStruct struct {
|
||||
HeadImage string `json:"headImage"`
|
||||
Name string `json:"name" validate:"max=15,min=3,required"`
|
||||
}
|
||||
params := UpdateUserInfoStruct{}
|
||||
|
||||
err := c.ShouldBindBodyWith(¶ms, binding.JSON)
|
||||
if err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if errMsg, err := base.ValidateInputStruct(¶ms); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
mUser := models.User{}
|
||||
err = mUser.UpdateUserInfoByUserId(userInfo.ID, map[string]interface{}{
|
||||
"head_image": params.HeadImage,
|
||||
"name": params.Name,
|
||||
})
|
||||
// 删除缓存
|
||||
global.UserToken.Delete(userInfo.Token)
|
||||
if err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
}
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
func (a *UserApi) UpdatePasssword(c *gin.Context) {
|
||||
type UpdatePasssStruct struct {
|
||||
OldPassword string `json:"oldPassword"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
|
||||
params := UpdatePasssStruct{}
|
||||
|
||||
err := c.ShouldBindBodyWith(¶ms, binding.JSON)
|
||||
if err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
}
|
||||
userInfo, _ := base.GetCurrentUserInfo(c)
|
||||
mUser := models.User{}
|
||||
if v, err := mUser.GetUserInfoByUid(userInfo.ID); err != nil {
|
||||
apiReturn.ErrorParamFomat(c, err.Error())
|
||||
return
|
||||
} else {
|
||||
if v.Password != cmn.PasswordEncryption(params.OldPassword) {
|
||||
// 旧密码不正确
|
||||
apiReturn.Error(c, global.Lang.Get("user.api_old_pass_error"))
|
||||
return
|
||||
}
|
||||
}
|
||||
res := global.Db.Model(&models.User{}).Where("id", userInfo.ID).Updates(map[string]interface{}{
|
||||
"password": cmn.PasswordEncryption(params.NewPassword),
|
||||
"token": "",
|
||||
})
|
||||
if res.Error != nil {
|
||||
apiReturn.ErrorDatabase(c, res.Error.Error())
|
||||
return
|
||||
}
|
||||
// 删除token
|
||||
global.UserToken.Delete(userInfo.Token)
|
||||
apiReturn.Success(c)
|
||||
}
|
||||
|
||||
// 获取推荐码
|
||||
func (a *UserApi) GetReferralCode(c *gin.Context) {
|
||||
currentUserInfo, _ := base.GetCurrentUserInfo(c)
|
||||
mUser := models.User{}
|
||||
userInfo, err := mUser.GetUserInfoByUid(currentUserInfo.ID)
|
||||
if err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 为空生成一个
|
||||
if userInfo.ReferralCode == "" {
|
||||
for {
|
||||
referralCode := cmn.BuildRandCode(8, cmn.RAND_CODE_MODE2)
|
||||
global.Logger.Debug("referralCode:", referralCode)
|
||||
|
||||
// 查询是否有重复的
|
||||
if row := global.Db.Find(&userInfo, "referral_code=?", referralCode).RowsAffected; row != 0 {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// 创建新的邀请码
|
||||
if err := global.Db.Model(&models.User{}).Where("id=?", userInfo.ID).Update("referral_code", referralCode).Error; err != nil {
|
||||
apiReturn.ErrorDatabase(c, err.Error())
|
||||
return
|
||||
} else {
|
||||
userInfo.ReferralCode = referralCode
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apiReturn.SuccessData(c, systemApiStructs.GetReferralCodeResp{ReferralCode: userInfo.ReferralCode})
|
||||
}
|
75
service/assets/conf.example.ini
Normal file
@ -0,0 +1,75 @@
|
||||
# ======================
|
||||
# Basic configuration
|
||||
# ======================
|
||||
[base]
|
||||
# Web run port. Default:9090
|
||||
http_port=3002
|
||||
# Database driver [mysql/sqlite(Default)]
|
||||
database_drive=sqlite
|
||||
# Cache driver [redis/memory(Default)]
|
||||
cache_drive=memory
|
||||
# Queue driver [redis/memory(Default)]
|
||||
queue_drive=memory
|
||||
# File upload path.
|
||||
source_path=./uploads
|
||||
# File cache path.
|
||||
source_temp_path=./runtime/temp
|
||||
|
||||
# ======================
|
||||
# Mysql database driver
|
||||
# ======================
|
||||
[mysql]
|
||||
host=127.0.0.1
|
||||
port=3306
|
||||
username=root
|
||||
password=root
|
||||
db_name=chatgpt
|
||||
wait_timeout=100
|
||||
|
||||
# ======================
|
||||
# sqlite database driver
|
||||
# ======================
|
||||
[sqlite]
|
||||
file_path=./database/database.db
|
||||
|
||||
# ======================
|
||||
# redis database driver
|
||||
# ======================
|
||||
[redis]
|
||||
address=127.0.0.1:6379
|
||||
password=
|
||||
prefix=sun_panel:
|
||||
db=0
|
||||
|
||||
# ================
|
||||
# proxy
|
||||
# ================
|
||||
[proxy]
|
||||
url=
|
||||
|
||||
# ======================
|
||||
# Automatically generated
|
||||
# Prohibit modification
|
||||
# ======================
|
||||
[build]
|
||||
conf_version=1
|
||||
; install_time=132456
|
||||
|
||||
|
||||
# ======================
|
||||
# Initial configuration
|
||||
# ======================
|
||||
# Automatically delete after successful initialization
|
||||
[init]
|
||||
# Administrator account (try to use email format; 6-16 digits)
|
||||
admin_username=admin
|
||||
# Administrator password (6-16 digits)
|
||||
admin_password=123456
|
||||
|
||||
# ======================
|
||||
# 支付宝支付相关配置
|
||||
# ======================
|
||||
[alipay]
|
||||
appid=
|
||||
private_key=
|
||||
alipay_public_key=
|
70
service/assets/lang/en-us.ini
Normal file
@ -0,0 +1,70 @@
|
||||
[lang_info]
|
||||
version=1.0
|
||||
soft_low_allow_version=1
|
||||
|
||||
[common]
|
||||
api_error_param_format=Incorrect parameter format
|
||||
api_password_error=Incorrect password
|
||||
api_update_fail=Update failed
|
||||
api_create_fail=Creation failed
|
||||
api_save_fail=Save failed
|
||||
api_get_fail=Get failed
|
||||
db_error=Database error
|
||||
app_name=Li Calendar Notes
|
||||
contact_admin=Please contact the administrator
|
||||
captcha_code_error=Invalid captcha code
|
||||
no_access=You do not have permission to perform this operation
|
||||
start_time=Start time
|
||||
end_time=End time
|
||||
|
||||
[captcha]
|
||||
api_void_captcha_id=Invalid captcha ID
|
||||
|
||||
[login]
|
||||
err_username_password=Incorrect username or password
|
||||
err_username_deactivation=User account is deactivated or not activated
|
||||
err_token_expire=Login session has expired
|
||||
err_not_login=Not logged in
|
||||
err_captcha_check_fail=Invalid captcha code
|
||||
|
||||
[register]
|
||||
mail_exist=Email address already in use
|
||||
username_exist=Username already in use
|
||||
err_code=Invalid email verification code
|
||||
unopened_register=Registration function is not yet open
|
||||
emailSuffix_error=Please use an email address with the suffix {EmailSuffix} to register
|
||||
|
||||
[style]
|
||||
err_title_already_exists=Title already exists
|
||||
err_class_name_already_exists=Class name already exists
|
||||
|
||||
[subject]
|
||||
err_title_already_exists=Name already exists
|
||||
|
||||
[admin]
|
||||
api_mail_setting_save_err=Failed to save email information
|
||||
api_mail_setting_get_err=Failed to get email information
|
||||
|
||||
[item]
|
||||
api_item_not_exists=Item does not exist
|
||||
|
||||
[mail]
|
||||
from=From
|
||||
|
||||
register_vcode_title=Welcome to register for {AppName}
|
||||
register_vcode_content=You are registering for {AppName}, and below is the verification code you need for registration, which is valid for {Minute} minutes. If this was not initiated by you, please ignore this email.
|
||||
|
||||
item_retrieval_password_title=You are retrieving the calendar access password
|
||||
item_retrieval_password_content=The access password for [{ItemName}] is:
|
||||
|
||||
reset_password_password_title=You are resetting the account password
|
||||
reset_password_password_content=The verification code you need to reset your password is:
|
||||
|
||||
reminder_title=You have a calendar reminder: {Title} starts at {Time}
|
||||
reminder_content=You have a reminder event in {ItemTitle}, which is as follows:
|
||||
reminder_event_title=Event title
|
||||
|
||||
send_mail_fail=Failed to send email
|
||||
|
||||
[user]
|
||||
api_old_pass_error=Incorrect old password
|
73
service/assets/lang/zh-cn.ini
Normal file
@ -0,0 +1,73 @@
|
||||
[lang_info]
|
||||
version=1.0
|
||||
soft_low_allow_version=1
|
||||
|
||||
[common]
|
||||
api_error_param_format=参数格式错误
|
||||
api_password_error=密码不正确
|
||||
api_update_fail=更新失败
|
||||
api_create_fail=创建失败
|
||||
api_save_fail=保存失败
|
||||
api_get_fail=获取失败
|
||||
db_error=数据库错误
|
||||
app_name=SAi-Chat
|
||||
contact_admin=请联系管理员
|
||||
captcha_code_error=验证码错误
|
||||
no_access=你当前无权限操作
|
||||
start_time=开始时间
|
||||
end_time=结束时间
|
||||
|
||||
[captcha]
|
||||
api_void_captcha_id=无效验证码ID
|
||||
api_captcha_fail=验证码错误
|
||||
|
||||
[login]
|
||||
err_username_password=账号或密码错误
|
||||
err_username_deactivation=账号已停用或未激活
|
||||
err_token_expire=登录状态已经过期
|
||||
err_not_login=未登录
|
||||
err_captcha_check_fail=验证码错误
|
||||
|
||||
|
||||
[register]
|
||||
mail_exist=邮箱已被使用
|
||||
username_exist=用户名已被使用
|
||||
err_code=邮箱验证码有误
|
||||
unopened_register=未开放注册功能
|
||||
emailSuffix_error=请使用后缀为{EmailSuffix}的邮箱地址注册
|
||||
|
||||
[style]
|
||||
err_title_already_exists=标题已存在
|
||||
err_class_name_already_exists=类名已存在
|
||||
|
||||
[subject]
|
||||
err_title_already_exists=名称已存在
|
||||
|
||||
[admin]
|
||||
api_mail_setting_save_err=邮箱信息保存失败
|
||||
api_mail_setting_get_err=邮箱信息获取失败
|
||||
|
||||
[item]
|
||||
api_item_not_exists=项目不存在
|
||||
|
||||
[mail]
|
||||
from=来自
|
||||
|
||||
register_vcode_title=欢迎注册{AppName}
|
||||
register_vcode_content=您正在注册{AppName},下方是注册所需的验证码,验证码有效期{Minute}分钟,如果这不是你本人的操作,请忽略此邮件。
|
||||
|
||||
item_retrieval_password_title=你正在找回日历访问密码
|
||||
item_retrieval_password_content=[{ItemName}]的访问密码是
|
||||
|
||||
reset_password_password_title=你正在重置账号的密码
|
||||
reset_password_password_content=你重置密码所需的验证码是
|
||||
|
||||
reminder_title=您有一个日历提醒:{Title}在{Time}开始
|
||||
reminder_content=您在{ItemTitle}中有一个提醒事件,具体如下:
|
||||
reminder_event_title=事件标题
|
||||
|
||||
|
||||
send_mail_fail=邮件发送失败
|
||||
|
||||
[user]
|
||||
api_old_pass_error=旧密码不正确
|
39
service/assets/readme.md
Normal file
@ -0,0 +1,39 @@
|
||||
## 将静态资源编译成go文件打包到可执行文件内
|
||||
|
||||
原版教程来源:https://blog.enianteam.com/u/sun/content/211
|
||||
|
||||
> 为了简化部署和减少出错的几率,将前端文件打包到可执行文件中,最终程序发布之后只有一个可执行文件
|
||||
|
||||
### 1. 安装
|
||||
** 注意:`...`必须带上 **
|
||||
```ssh
|
||||
go get github.com/go-bindata/go-bindata/...
|
||||
go get github.com/elazarl/go-bindata-assetfs/...
|
||||
|
||||
# go版本>=1.17 使用intsall方式
|
||||
go install -a -v github.com/go-bindata/go-bindata/...@latest
|
||||
go install -a -v github.com/elazarl/go-bindata-assetfs/...@latest
|
||||
```
|
||||
### 2. 安装成功后将 `GOPATH/bin` 加入环境变量
|
||||
|
||||
参考各自系统环境变量配置即可
|
||||
|
||||
|
||||
### 3. 压缩静态文件 到 asset目录
|
||||
以下命令在Windows的`powershell`可能会报错,可使用`cmd`执行
|
||||
```ssh
|
||||
# 开发环境,并非真实将所有文件编译,修改静态文件可以及时生效
|
||||
go-bindata-assetfs -debug -o=assets/bindata.go -pkg=assets static/... view/... # 多个
|
||||
go-bindata-assetfs -debug -o=assets/bindata.go -pkg=assets assets/...
|
||||
|
||||
# 正式环境,修改静态文件后需要重新编译
|
||||
go-bindata-assetfs -o=assets/bindata.go -pkg=assets assets/...
|
||||
```
|
||||
> 正式环境需要 去掉` -debug `
|
||||
|
||||
#### 参考文章
|
||||
Go | Go 语言打包静态文件以及如何与Gin一起使用Go-bindata
|
||||
https://www.jianshu.com/p/a7f5885679ef
|
||||
|
||||
[golang]Go内嵌静态资源go-bindata的安装及使用
|
||||
https://www.cnblogs.com/landv/p/11577213.html
|
1
service/assets/version
Normal file
@ -0,0 +1 @@
|
||||
1|1.0.0
|
38
service/global/cache.go
Normal file
@ -0,0 +1,38 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"sun-panel/lib/cache"
|
||||
"sun-panel/structs"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 缓存驱动
|
||||
const (
|
||||
CACHE_DRIVE_REDIS = "redis"
|
||||
CACHE_DRIVE_MEMORY = "memory"
|
||||
)
|
||||
|
||||
// 创建一个缓存区
|
||||
// | defaultExpiration:默认过期时长
|
||||
// | cleanupInterval:清理过期的key间隔 0.不清理
|
||||
// | name:缓存名称
|
||||
func NewCache[T any](defaultExpiration time.Duration, cleanupInterval time.Duration, name string) cache.Cacher[T] {
|
||||
drive := Config.GetValueString("base", "cache_drive")
|
||||
if drive == "" {
|
||||
drive = CACHE_DRIVE_MEMORY
|
||||
}
|
||||
var cacher cache.Cacher[T]
|
||||
Logger.Debugln("缓存驱动:", drive)
|
||||
switch drive {
|
||||
case CACHE_DRIVE_MEMORY:
|
||||
cacher = cache.NewGoCache[T](defaultExpiration, cleanupInterval)
|
||||
case CACHE_DRIVE_REDIS:
|
||||
redisConfig := structs.IniConfigRedis{}
|
||||
if err := Config.GetSection("redis", &redisConfig); err != nil {
|
||||
redisConfig.Prefix = ""
|
||||
}
|
||||
cacher = cache.NewRedisCache[T](RedisDb, redisConfig.Prefix+name, defaultExpiration, cleanupInterval)
|
||||
}
|
||||
|
||||
return cacher
|
||||
}
|
39
service/global/global.go
Normal file
@ -0,0 +1,39 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"sun-panel/initialize/database"
|
||||
"sun-panel/lib/cache"
|
||||
"sun-panel/lib/cmn/systemSetting"
|
||||
"sun-panel/lib/iniConfig"
|
||||
"sun-panel/lib/language"
|
||||
"sun-panel/models"
|
||||
|
||||
redis "github.com/redis/go-redis/v9"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
ISDOCKER = "" // 是否为docker模式运行
|
||||
RUNCODE = "debug" // 运行模式:debug | release
|
||||
// DB_MYSQL = "mysql"
|
||||
// DB_SQLITE = "sqlite"
|
||||
DB_DRIVER = database.SQLITE
|
||||
)
|
||||
|
||||
// var Log *cmn.LogStruct
|
||||
|
||||
var (
|
||||
Lang *language.LangStructObj
|
||||
|
||||
UserToken cache.Cacher[models.User]
|
||||
CUserToken cache.Cacher[string] // 用户token
|
||||
Logger *zap.SugaredLogger
|
||||
LoggerLevel = zap.NewAtomicLevel() // 支持通过http以及配置文件动态修改日志级别
|
||||
VerifyCodeCachePool cache.Cacher[string]
|
||||
Config *iniConfig.IniConfig
|
||||
Db *gorm.DB
|
||||
RedisDb *redis.Client
|
||||
SystemSetting *systemSetting.SystemSettingCache
|
||||
RateLimit *RateLimiter
|
||||
)
|
37
service/global/queue.go
Normal file
@ -0,0 +1,37 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"sun-panel/lib/queue"
|
||||
"sun-panel/lib/queue/queueMemory"
|
||||
"sun-panel/lib/queue/queueRedis"
|
||||
"sun-panel/structs"
|
||||
)
|
||||
|
||||
// 缓存驱动
|
||||
const (
|
||||
QUEUE_DRIVE_REDIS = "redis"
|
||||
QUEUE_DRIVE_MEMORY = "memory"
|
||||
)
|
||||
|
||||
// 创建一个队列
|
||||
// name:缓存名称
|
||||
func NewQueuer(name string) queue.Queuer {
|
||||
drive := Config.GetValueString("base", "queue_drive")
|
||||
if drive == "" {
|
||||
drive = CACHE_DRIVE_MEMORY
|
||||
}
|
||||
var queuer queue.Queuer
|
||||
Logger.Debugln("队列驱动:", drive)
|
||||
switch drive {
|
||||
case CACHE_DRIVE_MEMORY:
|
||||
queuer = queueMemory.New()
|
||||
case CACHE_DRIVE_REDIS:
|
||||
redisConfig := structs.IniConfigRedis{}
|
||||
if err := Config.GetSection("redis", &redisConfig); err != nil {
|
||||
redisConfig.Prefix = ""
|
||||
}
|
||||
queuer = queueRedis.New(RedisDb, redisConfig.Prefix+name)
|
||||
}
|
||||
|
||||
return queuer
|
||||
}
|
43
service/global/rateLimit.go
Normal file
@ -0,0 +1,43 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sun-panel/lib/cache"
|
||||
)
|
||||
|
||||
type RateLimiter struct {
|
||||
Minute cache.Cacher[int]
|
||||
Hour cache.Cacher[int]
|
||||
}
|
||||
|
||||
func (r *RateLimiter) MinuteAddOnce(userId uint) {
|
||||
key := "user_" + strconv.Itoa(int(userId))
|
||||
times := r.MinuteGet(userId) + 1
|
||||
// fmt.Println("fen册数", times)
|
||||
r.Minute.SetKeepExpiration(key, times)
|
||||
// r.Minute.SetDefault(key, times)
|
||||
}
|
||||
|
||||
func (r *RateLimiter) MinuteGet(userId uint) int {
|
||||
if v, ok := r.Minute.Get("user_" + strconv.Itoa(int(userId))); !ok {
|
||||
return 0
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RateLimiter) HourAddOnce(userId uint) {
|
||||
key := "user_" + strconv.Itoa(int(userId))
|
||||
times := r.HourGet(userId) + 1
|
||||
// fmt.Println("hour册数", times)
|
||||
r.Hour.SetKeepExpiration(key, times)
|
||||
// r.Hour.SetDefault(key, times)
|
||||
}
|
||||
|
||||
func (r *RateLimiter) HourGet(userId uint) int {
|
||||
if v, ok := r.Hour.Get("user_" + strconv.Itoa(int(userId))); !ok {
|
||||
return 0
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
66
service/go.mod
Normal file
@ -0,0 +1,66 @@
|
||||
module sun-panel
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/go-playground/locales v0.14.1
|
||||
github.com/go-playground/universal-translator v0.18.1
|
||||
github.com/go-playground/validator/v10 v10.11.2
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/mojocn/base64Captcha v1.3.5
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/redis/go-redis/v9 v9.0.5
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/shirou/gopsutil/v3 v3.23.3
|
||||
gitlab.com/tingshuo/go-diskstate v0.0.0-20191211131809-ee5e7223d03c
|
||||
go.uber.org/zap v1.24.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gorm.io/driver/mysql v1.5.0
|
||||
gorm.io/driver/sqlite v1.5.0
|
||||
gorm.io/gorm v1.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.8.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.4 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/crypto v0.8.0 // indirect
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
163
service/go.sum
Normal file
@ -0,0 +1,163 @@
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
|
||||
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
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.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
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.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
|
||||
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
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 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
|
||||
github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
|
||||
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
||||
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
|
||||
github.com/shoenig/go-m1cpu v0.1.4 h1:SZPIgRM2sEF9NJy50mRHu9PKGwxyyTTJIWvCtgVbozs=
|
||||
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
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.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
gitlab.com/tingshuo/go-diskstate v0.0.0-20191211131809-ee5e7223d03c h1:oX6xxxK4o3t4Zq6LPliClUj7Owszg9eelw91nrpNLE4=
|
||||
gitlab.com/tingshuo/go-diskstate v0.0.0-20191211131809-ee5e7223d03c/go.mod h1:FGwp/bRO7lNElWWEvI83K2AELhrweAzh7+wJ51SlmIs=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 h1:TbGuee8sSq15Iguxu4deQ7+Bqq/d2rsQejGcEtADAMQ=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
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=
|
||||
gorm.io/driver/mysql v1.5.0 h1:6hSAT5QcyIaty0jfnff0z0CLDjyRgZ8mlMHLqSt7uXM=
|
||||
gorm.io/driver/mysql v1.5.0/go.mod h1:FFla/fJuCvyTi7rJQd27qlNX2v3L6deTR1GgTjSOLPo=
|
||||
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
|
||||
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
|
||||
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
178
service/initialize/A_ENTER.go
Normal file
@ -0,0 +1,178 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"sun-panel/global"
|
||||
"sun-panel/initialize/cUserToken"
|
||||
"sun-panel/initialize/config"
|
||||
"sun-panel/initialize/database"
|
||||
"sun-panel/initialize/lang"
|
||||
"sun-panel/initialize/other"
|
||||
"sun-panel/initialize/redis"
|
||||
"sun-panel/initialize/runlog"
|
||||
"sun-panel/initialize/systemSettingCache"
|
||||
"sun-panel/initialize/userToken"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/models"
|
||||
"sun-panel/structs"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var DB_DRIVER = database.SQLITE
|
||||
|
||||
// var RUNCODE = "debug"
|
||||
// var ISDOCER = "" // 是否为docker模式
|
||||
|
||||
func InitApp() error {
|
||||
gin.SetMode(global.RUNCODE) // GIN 运行模式
|
||||
|
||||
// 日志
|
||||
if logger, err := runlog.InitRunlog(global.RUNCODE, "running.log"); err != nil {
|
||||
log.Panicln("日志初始化错误", err)
|
||||
panic(err)
|
||||
} else {
|
||||
global.Logger = logger
|
||||
}
|
||||
|
||||
// 命令行运行
|
||||
CommandRun()
|
||||
|
||||
// 配置初始化
|
||||
{
|
||||
if config, err := config.ConfigInit(); err != nil {
|
||||
global.Logger.Errorln("配置初始化错误", err)
|
||||
return err
|
||||
} else {
|
||||
global.Config = config
|
||||
}
|
||||
}
|
||||
|
||||
// 多语言初始化
|
||||
lang.LangInit("zh-cn") // en-us
|
||||
|
||||
DatabaseConnect()
|
||||
|
||||
// Redis 连接
|
||||
{
|
||||
// 判断是否有使用redis的驱动,没有将不连接
|
||||
cacheDrive := global.Config.GetValueString("base", "cache_drive")
|
||||
queueDrive := global.Config.GetValueString("base", "queue_drive")
|
||||
if cacheDrive == "redis" || queueDrive == "redis" {
|
||||
redisConfig := structs.IniConfigRedis{}
|
||||
global.Config.GetSection("redis", &redisConfig)
|
||||
rdb, err := redis.InitRedis(redis.Options{
|
||||
Addr: redisConfig.Address,
|
||||
Password: redisConfig.Password,
|
||||
DB: redisConfig.Db,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Panicln("Redis初始化错误", err)
|
||||
panic(err)
|
||||
// return err
|
||||
}
|
||||
global.RedisDb = rdb
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化用户token
|
||||
global.UserToken = userToken.InitUserToken()
|
||||
global.CUserToken = cUserToken.InitCUserToken()
|
||||
|
||||
// 其他的初始化
|
||||
global.VerifyCodeCachePool = other.InitVerifyCodeCachePool()
|
||||
global.SystemSetting = systemSettingCache.InItSystemSettingCache()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DatabaseConnect() {
|
||||
// 数据库连接 - 开始
|
||||
var dbClientInfo database.DbClient
|
||||
databaseDrive := global.Config.GetValueStringOrDefault("base", "database_drive")
|
||||
if databaseDrive == database.MYSQL {
|
||||
dbClientInfo = &database.MySQLConfig{
|
||||
Username: global.Config.GetValueStringOrDefault("mysql", "username"),
|
||||
Password: global.Config.GetValueStringOrDefault("mysql", "password"),
|
||||
Host: global.Config.GetValueStringOrDefault("mysql", "host"),
|
||||
Port: global.Config.GetValueStringOrDefault("mysql", "port"),
|
||||
Database: global.Config.GetValueStringOrDefault("mysql", "db_name"),
|
||||
WaitTimeout: global.Config.GetValueInt("mysql", "wait_timeout"),
|
||||
}
|
||||
} else {
|
||||
dbClientInfo = &database.SQLiteConfig{
|
||||
Filename: global.Config.GetValueStringOrDefault("sqlite", "file_path"),
|
||||
}
|
||||
}
|
||||
|
||||
if db, err := database.DbInit(dbClientInfo); err != nil {
|
||||
log.Panicln("数据库初始化错误", err)
|
||||
panic(err)
|
||||
} else {
|
||||
global.Db = db
|
||||
models.Db = global.Db
|
||||
}
|
||||
|
||||
database.CreateDatabase(databaseDrive, global.Db)
|
||||
|
||||
database.NotFoundAndCreateUser(global.Db)
|
||||
}
|
||||
|
||||
// 命令行运行
|
||||
func CommandRun() {
|
||||
var (
|
||||
cfg bool
|
||||
pwd bool
|
||||
)
|
||||
|
||||
flag.BoolVar(&cfg, "config", false, "生成配置文件")
|
||||
flag.BoolVar(&pwd, "password-reset", false, "重置第一个用户的密码")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if cfg {
|
||||
// 生成配置文件
|
||||
fmt.Println("正在生成配置文件")
|
||||
cmn.AssetsTakeFileToPath("conf.example.ini", "conf/conf.example.ini")
|
||||
cmn.AssetsTakeFileToPath("conf.example.ini", "conf/conf.ini")
|
||||
fmt.Println("配置文件已经创建 conf/conf.ini ", "请按照自己的需求修改")
|
||||
os.Exit(0) // 务必退出
|
||||
} else if pwd {
|
||||
|
||||
// 配置初始化
|
||||
config, _ := config.ConfigInit()
|
||||
global.Config = config
|
||||
|
||||
// 重置密码
|
||||
DatabaseConnect()
|
||||
userInfo := models.User{}
|
||||
if err := global.Db.First(&userInfo).Error; err != nil {
|
||||
fmt.Println("ERROR", err.Error())
|
||||
os.Exit(0) // 务必退出
|
||||
}
|
||||
|
||||
newPassword := "12345678"
|
||||
|
||||
updateInfo := models.User{
|
||||
Password: cmn.PasswordEncryption(newPassword),
|
||||
Token: "",
|
||||
}
|
||||
if err := global.Db.Select("Password", "Token").Where("id=?", userInfo.ID).Updates(&updateInfo).Error; err != nil {
|
||||
fmt.Println("ERROR", err.Error())
|
||||
os.Exit(0) // 务必退出
|
||||
}
|
||||
|
||||
fmt.Println("密码已经重置成功,以下是账号信息")
|
||||
fmt.Println("用户名 ", userInfo.Username)
|
||||
fmt.Println("密码 ", newPassword)
|
||||
os.Exit(0) // 务必退出
|
||||
} else {
|
||||
return
|
||||
}
|
||||
os.Exit(0) // 务必退出
|
||||
}
|
16
service/initialize/cUserToken/userToken.go
Normal file
@ -0,0 +1,16 @@
|
||||
package cUserToken
|
||||
|
||||
import (
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cache"
|
||||
|
||||
"time"
|
||||
)
|
||||
|
||||
func InitCUserToken() cache.Cacher[string] {
|
||||
return global.NewCache[string](72*time.Hour, 48*time.Hour, "CUserToken")
|
||||
}
|
||||
|
||||
// func InitVerifyCodeCachePool() {
|
||||
// global.VerifyCodeCachePool = cache.NewGoCache(10*time.Minute, 60*time.Second)
|
||||
// }
|
90
service/initialize/config/config.go
Normal file
@ -0,0 +1,90 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/lib/iniConfig"
|
||||
)
|
||||
|
||||
func getDefaultConfig() map[string]map[string]string {
|
||||
return map[string]map[string]string{
|
||||
"base": {
|
||||
"http_port": "9090",
|
||||
"source_path": "./files", // 存放文件的路径
|
||||
"source_temp_path": "./files/temp", // 存放文件的缓存路径
|
||||
},
|
||||
"sqlite": {
|
||||
"file_path": "./database.db",
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ConfigInit() (*iniConfig.IniConfig, error) {
|
||||
|
||||
// 配置文件初始化
|
||||
if config, err, errCode := Conf(getDefaultConfig()); err != nil && errCode == 0 {
|
||||
// 抛出错误
|
||||
cmn.Pln(cmn.LOG_ERROR, "配置文件创建错误:"+err.Error())
|
||||
os.Exit(1)
|
||||
return nil, err
|
||||
} else if errCode == 1 {
|
||||
// 配置文件不存在,进行创建
|
||||
if err := CreateConfExample("conf.example.ini", "conf.ini"); err != nil {
|
||||
cmn.Pln(cmn.LOG_ERROR, "配置文件创建错误:"+err.Error())
|
||||
os.Exit(1)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
global.Logger.Errorln("配置文件已经自动生成'conf/conf.ini',将再次读取配置")
|
||||
// 创建成功再次读取文件
|
||||
if configAgain, errAgain, _ := Conf(getDefaultConfig()); errAgain != nil {
|
||||
return nil, errAgain
|
||||
} else {
|
||||
global.Logger.Errorln("尝试读取配置文件'conf/conf.ini',二次读取配置文件成功")
|
||||
return configAgain, nil
|
||||
}
|
||||
} else {
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 配置初始化
|
||||
// errCode=1 说明初始化流程
|
||||
func Conf(defaultConfig map[string]map[string]string) (config *iniConfig.IniConfig, err error, errCode int) {
|
||||
CreateConfExample("conf.example.ini", "conf.example.ini")
|
||||
exists, err := cmn.PathExists("conf/conf.ini")
|
||||
if exists {
|
||||
config = iniConfig.NewIniConfig("conf/conf.ini") // 读取配置
|
||||
config.Default = defaultConfig
|
||||
} else if err != nil {
|
||||
|
||||
} else {
|
||||
// docker 运行模式,生成配置文件
|
||||
if global.ISDOCKER != "" {
|
||||
cmn.AssetsTakeFileToPath("conf.example.ini", "conf/conf.ini")
|
||||
config = iniConfig.NewIniConfig("conf/conf.ini") // 读取配置
|
||||
config.Default = defaultConfig
|
||||
} else {
|
||||
errCode = 1
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 生成示例配置文件
|
||||
func CreateConfExample(confName string, targetName string) (err error) {
|
||||
// 查看配置示例文件是否存在,不存在创建(分别为示例配置和配置文件)
|
||||
exists, err := cmn.PathExists("conf/" + targetName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
if err = cmn.AssetsTakeFileToPath(confName, "conf/"+targetName); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
150
service/initialize/database/connect.go
Normal file
@ -0,0 +1,150 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/models"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
_ "gorm.io/driver/mysql"
|
||||
"gorm.io/driver/sqlite"
|
||||
_ "gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
MYSQL = "mysql"
|
||||
SQLITE = "sqlite"
|
||||
)
|
||||
|
||||
type DbClient interface {
|
||||
Connect() (db *gorm.DB, err error)
|
||||
}
|
||||
|
||||
type MySQLConfig struct {
|
||||
Username string
|
||||
Password string
|
||||
Host string
|
||||
Port string
|
||||
Database string
|
||||
WaitTimeout int
|
||||
}
|
||||
|
||||
type SQLiteConfig struct {
|
||||
Filename string
|
||||
}
|
||||
|
||||
func DbInit(dbClient DbClient) (db *gorm.DB, dbErr error) {
|
||||
db, dbErr = dbClient.Connect()
|
||||
if dbErr != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Connect mysql连接
|
||||
func (d *MySQLConfig) Connect() (db *gorm.DB, err error) {
|
||||
dsn := d.Username + ":" + d.Password + "@tcp(" + d.Host + ":" + d.Port + ")/" + d.Database + "?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
Logger: GetLogger(),
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
// TablePrefix: "blog_",
|
||||
SingularTable: true,
|
||||
},
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
})
|
||||
sqlDb, _ := db.DB()
|
||||
sqlDb.SetMaxIdleConns(10) // SetMaxIdleConns 设置空闲连接池中连接的最大数量
|
||||
sqlDb.SetMaxOpenConns(100) // SetMaxOpenConns 设置打开数据库连接的最大数量。
|
||||
wait_timeout := d.WaitTimeout
|
||||
sqlDb.SetConnMaxLifetime(time.Duration(wait_timeout * int(time.Second))) // SetConnMaxLifetime 设置了连接可复用的最大时间。
|
||||
return
|
||||
}
|
||||
|
||||
// Connect sqllite3连接
|
||||
func (d *SQLiteConfig) Connect() (db *gorm.DB, err error) {
|
||||
filePath := d.Filename
|
||||
exists := false
|
||||
if exists, err = cmn.PathExists(path.Dir(filePath)); err != nil {
|
||||
return
|
||||
} else {
|
||||
|
||||
// 创建文件夹
|
||||
if !exists {
|
||||
if err = os.MkdirAll(path.Dir(filePath), 0666); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
db, err = gorm.Open(sqlite.Open(filePath), &gorm.Config{
|
||||
Logger: GetLogger(),
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
SingularTable: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 日志
|
||||
func GetLogger() logger.Interface {
|
||||
return logger.New(
|
||||
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
|
||||
logger.Config{
|
||||
SlowThreshold: time.Second, // 慢 SQL 阈值
|
||||
LogLevel: logger.Warn, // 日志级别
|
||||
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
|
||||
Colorful: true, // 彩色打印
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
// 创建数据库
|
||||
func CreateDatabase(driver string, db *gorm.DB) error {
|
||||
|
||||
// mysql特殊处理
|
||||
if driver == MYSQL {
|
||||
db = db.Set("gorm:table_options", "ENGINE=InnoDB")
|
||||
}
|
||||
|
||||
// 创建数据表
|
||||
err := db.AutoMigrate(
|
||||
&models.User{},
|
||||
&models.SystemSetting{},
|
||||
&models.ItemIcon{},
|
||||
&models.UserConfig{},
|
||||
&models.File{},
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化一个用户,一个用户都没有的时候创建一个
|
||||
func NotFoundAndCreateUser(db *gorm.DB) error {
|
||||
fUser := models.User{}
|
||||
if err := db.First(&fUser).Error; err != nil {
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
username := "admin@sun.cc"
|
||||
fUser.Mail = username
|
||||
fUser.Username = username
|
||||
fUser.Name = username
|
||||
fUser.Status = 1
|
||||
fUser.Role = 1
|
||||
fUser.Password = cmn.PasswordEncryption("12345678")
|
||||
|
||||
if errCreate := db.Create(&fUser).Error; errCreate != nil {
|
||||
return errCreate
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
39
service/initialize/lang/lang.go
Normal file
@ -0,0 +1,39 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/lib/language"
|
||||
)
|
||||
|
||||
func LangInit(lang string) {
|
||||
filename := "lang/" + lang + ".ini"
|
||||
exists, err := cmn.PathExists(filename)
|
||||
if err != nil {
|
||||
global.Logger.Errorln("语言文件不存在", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 生成语言文件
|
||||
if !exists {
|
||||
global.Logger.Infoln("输出语言文件:", filename)
|
||||
err := cmn.AssetsTakeFileToPath("lang/zh-cn.ini", "lang/zh-cn.ini")
|
||||
if err != nil {
|
||||
global.Logger.Errorln("输出语言文件出错:", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
err = cmn.AssetsTakeFileToPath("lang/en-us.ini", "lang/en-us.ini")
|
||||
if err != nil {
|
||||
global.Logger.Errorln("输出语言文件出错:", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
exists, err = cmn.PathExists(filename)
|
||||
if err != nil || !exists {
|
||||
global.Logger.Errorln("语言文件不存在:", filename)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
global.Lang = language.NewLang(filename)
|
||||
}
|
12
service/initialize/other/verifyCodeCachePool.go
Normal file
@ -0,0 +1,12 @@
|
||||
package other
|
||||
|
||||
import (
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cache"
|
||||
"time"
|
||||
)
|
||||
|
||||
func InitVerifyCodeCachePool() cache.Cacher[string] {
|
||||
return global.NewCache[string](10*time.Minute, 10*time.Minute, "VerifyCodeCachePool")
|
||||
|
||||
}
|
17
service/initialize/rateLimitCache/openai.go
Normal file
@ -0,0 +1,17 @@
|
||||
package rateLimitCache
|
||||
|
||||
import (
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cache"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 速率限制分钟级别
|
||||
func InitMinute() cache.Cacher[int] {
|
||||
return global.NewCache[int](1*time.Minute, 1*time.Hour, "RateLimitCacheMinute")
|
||||
}
|
||||
|
||||
// 速率限制小时级别
|
||||
func InitHour() cache.Cacher[int] {
|
||||
return global.NewCache[int](1*time.Hour, 2*time.Hour, "RateLimitCacheHour")
|
||||
}
|
28
service/initialize/redis/redis.go
Normal file
@ -0,0 +1,28 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Addr string // localhost:6379
|
||||
Password string // 没有密码,默认值
|
||||
DB int // 默认DB 0
|
||||
}
|
||||
|
||||
func InitRedis(options Options) (*redis.Client, error) {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: options.Addr,
|
||||
Password: options.Password,
|
||||
DB: options.DB,
|
||||
})
|
||||
|
||||
// 验证连接是否成功
|
||||
ctx := context.Background()
|
||||
if _, err := rdb.Ping(ctx).Result(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rdb, nil
|
||||
}
|
26
service/initialize/runlog/runlog.go
Normal file
@ -0,0 +1,26 @@
|
||||
package runlog
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func InitRunlog(runmode string, filePath string) (*zap.SugaredLogger, error) {
|
||||
|
||||
runtimePath := "./runtime/runlog"
|
||||
if err := os.MkdirAll(runtimePath, 0777); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var level zap.AtomicLevel
|
||||
if runmode == "debug" {
|
||||
level = zap.NewAtomicLevelAt(zap.DebugLevel)
|
||||
} else {
|
||||
level = global.LoggerLevel
|
||||
}
|
||||
|
||||
logger := cmn.InitLogger(runtimePath+"/"+filePath, level)
|
||||
return logger, nil
|
||||
}
|
13
service/initialize/systemSettingCache/systemSettingCache.go
Normal file
@ -0,0 +1,13 @@
|
||||
package systemSettingCache
|
||||
|
||||
import (
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cmn/systemSetting"
|
||||
"time"
|
||||
)
|
||||
|
||||
func InItSystemSettingCache() *systemSetting.SystemSettingCache {
|
||||
return &systemSetting.SystemSettingCache{
|
||||
Cache: global.NewCache[interface{}](5*time.Hour, -1, "systemSettingCache"),
|
||||
}
|
||||
}
|
17
service/initialize/userToken/userToken.go
Normal file
@ -0,0 +1,17 @@
|
||||
package userToken
|
||||
|
||||
import (
|
||||
"sun-panel/global"
|
||||
"sun-panel/lib/cache"
|
||||
"sun-panel/models"
|
||||
|
||||
"time"
|
||||
)
|
||||
|
||||
func InitUserToken() cache.Cacher[models.User] {
|
||||
return global.NewCache[models.User](1*time.Minute, 1*time.Hour, "UserToken")
|
||||
}
|
||||
|
||||
// func InitVerifyCodeCachePool() {
|
||||
// global.VerifyCodeCachePool = cache.NewGoCache(10*time.Minute, 60*time.Second)
|
||||
// }
|
33
service/lib/cache/base.go
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 缓存接口-支持Redis和内存使用
|
||||
type Cacher[T any] interface {
|
||||
// 设置
|
||||
Set(k string, v T, d time.Duration)
|
||||
|
||||
// 取值
|
||||
Get(k string) (T, bool)
|
||||
|
||||
// 设置-过期时间采用默认值
|
||||
SetDefault(k string, v T)
|
||||
|
||||
// 删除
|
||||
Delete(k string)
|
||||
|
||||
// 只有在给定Key项尚未存在,或者现有项已过期时,才能将项添加到缓存中。否则返回错误。
|
||||
// Add(k string, v T, d time.Duration)
|
||||
// IncrementInt(k string, n int) (num int, err error)
|
||||
|
||||
// 设置值,但不重置过期时间
|
||||
SetKeepExpiration(k string, v T)
|
||||
|
||||
// 项目总数
|
||||
ItemCount() (int64, error)
|
||||
|
||||
// 清空
|
||||
Flush()
|
||||
}
|
96
service/lib/cache/gocache.go
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
// 参考:https://blog.csdn.net/u014459543/article/details/108429469
|
||||
type GoCacheStruct[T any] struct {
|
||||
gocahce *cache.Cache
|
||||
Result T
|
||||
}
|
||||
|
||||
type GoCacheValue[T any] struct {
|
||||
Value T
|
||||
}
|
||||
|
||||
// 创建一个goCache结构体
|
||||
// cache.New(5*time.Minute, 60*time.Second),清理过期的item间隔 0.不清理
|
||||
func NewGoCache[T any](defaultExpiration time.Duration, cleanupInterval time.Duration) *GoCacheStruct[T] {
|
||||
cacheAdapter := cache.New(defaultExpiration, cleanupInterval)
|
||||
return &GoCacheStruct[T]{
|
||||
gocahce: cacheAdapter,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GoCacheStruct[T]) Set(k string, x T, d time.Duration) {
|
||||
c.gocahce.Set(k, GoCacheValue[T]{Value: x}, d)
|
||||
}
|
||||
|
||||
func (c *GoCacheStruct[T]) Get(k string) (T, bool) {
|
||||
if v, ok := c.gocahce.Get(k); ok {
|
||||
if value, okv := v.(GoCacheValue[T]); okv {
|
||||
return value.Value, true
|
||||
}
|
||||
}
|
||||
return c.Result, false
|
||||
}
|
||||
|
||||
// 设置cache 无时间参数
|
||||
func (c *GoCacheStruct[T]) SetDefault(k string, v T) {
|
||||
c.gocahce.SetDefault(k, GoCacheValue[T]{Value: v})
|
||||
}
|
||||
|
||||
// 设置并保持原始的过期时间
|
||||
func (c *GoCacheStruct[T]) SetKeepExpiration(k string, v T) {
|
||||
_, expirationTime, ok := c.gocahce.GetWithExpiration(k)
|
||||
|
||||
now := time.Now()
|
||||
differ := expirationTime.Sub(now)
|
||||
// 如果 过期值不为零值 && 未过期 && 过期时间大于现在的时间
|
||||
// 将保持不变原始的过期时间来计算时间
|
||||
if !expirationTime.IsZero() && ok && differ > 0 {
|
||||
// newExpiration := now.Unix() + int64(math.Round(differ.Seconds()))
|
||||
// fmt.Println("旧的过期时间", expirationTime.Unix())
|
||||
// fmt.Println("时间限制差", math.Round(differ.Seconds()))
|
||||
// fmt.Println("新的过期时间", newExpiration)
|
||||
c.gocahce.Set(k, GoCacheValue[T]{Value: v}, differ)
|
||||
} else {
|
||||
c.gocahce.SetDefault(k, GoCacheValue[T]{Value: v})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除 cache
|
||||
func (c *GoCacheStruct[T]) Delete(k string) {
|
||||
c.gocahce.Delete(k)
|
||||
}
|
||||
|
||||
// Add() 加入缓存
|
||||
func (c *GoCacheStruct[T]) Add(k string, v T, d time.Duration) {
|
||||
c.gocahce.Add(k, GoCacheValue[T]{Value: v}, d)
|
||||
}
|
||||
|
||||
// IncrementInt() 对已存在的key 值自增n
|
||||
func (c *GoCacheStruct[T]) IncrementInt(k string, n int) (num int, err error) {
|
||||
return c.gocahce.IncrementInt(k, n)
|
||||
}
|
||||
|
||||
// ItemCount 获取已存在key的数量
|
||||
func (c *GoCacheStruct[T]) ItemCount() (int64, error) {
|
||||
return int64(c.gocahce.ItemCount()), nil
|
||||
}
|
||||
|
||||
// Flush 删除当前已存在的所有key
|
||||
func (c *GoCacheStruct[T]) Flush() {
|
||||
c.gocahce.Flush()
|
||||
}
|
||||
|
||||
// func (c *GoCacheStruct[T]) encode(value T) ([]byte, error) {
|
||||
// return json.Marshal(value)
|
||||
// }
|
||||
|
||||
// func (c *GoCacheStruct[T]) decode(valueByte []byte, value T) error {
|
||||
// return json.Unmarshal(valueByte, value)
|
||||
// }
|
195
service/lib/cache/redis.go
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
redis "github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type RedisCacheStruct[T any] struct {
|
||||
Redis *redis.Client
|
||||
Ctx context.Context
|
||||
HashKey string
|
||||
Result T
|
||||
DefaultExpiration time.Duration
|
||||
CleanupInterval time.Duration
|
||||
}
|
||||
|
||||
type RedisValue[T any] struct {
|
||||
ExpirationTimeStamp int64
|
||||
IsExpiration bool // 是否有过期时间 false // 不过期
|
||||
Value T
|
||||
}
|
||||
|
||||
// cache.New(5*time.Minute, 60*time.Second)
|
||||
func NewRedisCache[T any](redisDb *redis.Client, hashKey string, defaultExpiration time.Duration, cleanupInterval time.Duration) *RedisCacheStruct[T] {
|
||||
obj := RedisCacheStruct[T]{
|
||||
Redis: redisDb,
|
||||
Ctx: context.Background(),
|
||||
HashKey: hashKey,
|
||||
DefaultExpiration: defaultExpiration,
|
||||
CleanupInterval: cleanupInterval,
|
||||
}
|
||||
|
||||
// 创建定时器判断是否过期
|
||||
if obj.CleanupInterval.Seconds() > 0 {
|
||||
go obj.expirationVerification()
|
||||
}
|
||||
|
||||
return &obj
|
||||
}
|
||||
|
||||
func (r *RedisCacheStruct[T]) Set(k string, v T, d time.Duration) {
|
||||
valueEncode := ""
|
||||
value := RedisValue[T]{}
|
||||
|
||||
// 设置过期时间
|
||||
if d.Seconds() > 0 {
|
||||
value.IsExpiration = true
|
||||
value.ExpirationTimeStamp = time.Now().Add(d).Unix()
|
||||
} else {
|
||||
value.IsExpiration = false // 不过期
|
||||
}
|
||||
|
||||
value.Value = v
|
||||
if j, e := json.Marshal(value); e == nil {
|
||||
valueEncode = string(j)
|
||||
}
|
||||
|
||||
r.Redis.HSet(r.Ctx, r.HashKey, k, valueEncode)
|
||||
// second := d.Seconds()
|
||||
// if second > 0 {
|
||||
// // 设置过期时间
|
||||
// err := r.Redis.Do(r.Ctx, "SETEX", r.HashKey+k, second, valueEncode).Err()
|
||||
// fmt.Println("设置结果", err)
|
||||
// } else {
|
||||
// r.Redis.HSet(r.Ctx, r.HashKey, k, valueEncode)
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
func (r *RedisCacheStruct[T]) Get(k string) (T, bool) {
|
||||
var valueEncode []byte
|
||||
value := RedisValue[T]{}
|
||||
cmd := r.Redis.HGet(r.Ctx, r.HashKey, k)
|
||||
if err := cmd.Scan(&valueEncode); err != nil {
|
||||
// log.Println(err)
|
||||
return r.Result, false
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(valueEncode, &value); err != nil {
|
||||
// log.Println(err)
|
||||
return r.Result, false
|
||||
}
|
||||
|
||||
// 已过期,清理掉key
|
||||
if value.IsExpiration && time.Now().Unix() > value.ExpirationTimeStamp {
|
||||
r.Delete(k)
|
||||
return r.Result, false
|
||||
}
|
||||
|
||||
return value.Value, true
|
||||
}
|
||||
|
||||
// 设置cache 无时间参数
|
||||
func (r *RedisCacheStruct[T]) SetDefault(k string, v T) {
|
||||
r.Set(k, v, r.DefaultExpiration)
|
||||
}
|
||||
|
||||
// 设置并保持原始的过期时间
|
||||
func (r *RedisCacheStruct[T]) SetKeepExpiration(k string, v T) {
|
||||
var valueEncode []byte
|
||||
value := RedisValue[T]{}
|
||||
cmd := r.Redis.HGet(r.Ctx, r.HashKey, k)
|
||||
if err := cmd.Scan(&valueEncode); err != nil {
|
||||
// fmt.Println("使用默认的过期时间")
|
||||
r.SetDefault(k, v)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(valueEncode, &value); err != nil {
|
||||
// fmt.Println("使用默认的过期时间")
|
||||
r.SetDefault(k, v)
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
timeDiffer := value.ExpirationTimeStamp - now.Unix()
|
||||
|
||||
// 如果设置了过期时间并且过期时间大于现在将保留原始的过期时间
|
||||
if value.IsExpiration && timeDiffer > 0 {
|
||||
// fmt.Println("重新计算过期时间")
|
||||
// fmt.Println("旧的过期时间", value.ExpirationTimeStamp)
|
||||
// fmt.Println("时间限制差", timeDiffer)
|
||||
// fmt.Println("新的过期时间", now.Unix()+timeDiffer)
|
||||
r.Set(k, v, time.Second*time.Duration(timeDiffer))
|
||||
} else {
|
||||
// fmt.Println("使用默认的过期时间")
|
||||
r.SetDefault(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除 cache
|
||||
func (r *RedisCacheStruct[T]) Delete(k string) {
|
||||
r.Redis.HDel(r.Ctx, r.HashKey, k)
|
||||
}
|
||||
|
||||
// Add() 加入缓存
|
||||
// func (r *RedisCacheStruct[T]) Add(k string, v T, d time.Duration) {
|
||||
// c.gocahce.Add(k, x, d)
|
||||
// }
|
||||
|
||||
// IncrementInt() 对已存在的key 值自增n
|
||||
// func (r *RedisCacheStruct[T]) IncrementInt(k string, n int) (num int, err error) {
|
||||
// if err := r.Redis.HIncrBy(r.Ctx, r.HashKey, k, int64(n)).Err(); err != nil {
|
||||
// return num, err
|
||||
// }
|
||||
|
||||
// if v, ok := r.Get(k); ok {
|
||||
// switch T {
|
||||
// case int:
|
||||
|
||||
// }
|
||||
// if vint, okint := v.(int); okint {
|
||||
|
||||
// }
|
||||
// }
|
||||
// return c.gocahce.IncrementInt(k, n)
|
||||
// }
|
||||
|
||||
// ItemCount 获取已存在key的数量
|
||||
func (r *RedisCacheStruct[T]) ItemCount() (int64, error) {
|
||||
if count, err := r.Redis.HLen(r.Ctx, r.HashKey).Result(); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return count, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Flush 删除当前已存在的所有key
|
||||
func (r *RedisCacheStruct[T]) Flush() {
|
||||
r.Redis.Del(r.Ctx, r.HashKey)
|
||||
}
|
||||
|
||||
// 定时清理过期验证
|
||||
func (r *RedisCacheStruct[T]) expirationVerification() {
|
||||
ticker := time.NewTicker(r.CleanupInterval)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if fields, err := r.Redis.HKeys(r.Ctx, r.HashKey).Result(); err == nil {
|
||||
for _, v := range fields {
|
||||
// r.Redis.HGet(r.Ctx, r.HashKey, v)
|
||||
r.Get(v)
|
||||
// fmt.Println("redis定时器", v)
|
||||
}
|
||||
}
|
||||
// case <-j.stop:
|
||||
// ticker.Stop()
|
||||
// return
|
||||
}
|
||||
}
|
||||
}
|
52
service/lib/captcha/captcha.go
Normal file
@ -0,0 +1,52 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"sun-panel/global"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
var Store = base64Captcha.DefaultMemStore
|
||||
|
||||
func NewDriver(width, height int) *base64Captcha.DriverString {
|
||||
driver := new(base64Captcha.DriverString)
|
||||
driver.Height = height
|
||||
driver.Width = width
|
||||
driver.NoiseCount = 0
|
||||
driver.ShowLineOptions = base64Captcha.OptionShowSlimeLine | base64Captcha.OptionShowHollowLine
|
||||
driver.Length = 4
|
||||
driver.Source = "1234567890qwertyuipkjhgfdsazxcvbnm"
|
||||
driver.Fonts = []string{"wqy-microhei.ttc"}
|
||||
return driver
|
||||
}
|
||||
|
||||
// 生成图形验证码
|
||||
func GenerateCaptchaHandler(id string, width, height int) string {
|
||||
var driver = NewDriver(width, height).ConvertFonts()
|
||||
c := base64Captcha.NewCaptcha(driver, Store)
|
||||
_, content, answer := c.Driver.GenerateIdQuestionAnswer()
|
||||
|
||||
item, _ := c.Driver.DrawCaptcha(content)
|
||||
c.Store.Set(id, answer)
|
||||
return item.EncodeB64string()
|
||||
}
|
||||
|
||||
// 验证
|
||||
func CaptchaVerifyHandle(id, vcode string) bool {
|
||||
return Store.Verify(id, vcode, true)
|
||||
}
|
||||
|
||||
// 根据key获取验证码ID
|
||||
func CaptchaGetIdByCookieHeader(c *gin.Context, key string) (captchaId string, err error) {
|
||||
|
||||
captchaId, err = c.Cookie("CaptchaId")
|
||||
if err != nil {
|
||||
global.Logger.Errorf("failed to get captchaId from cookie, err:%+v\n", err)
|
||||
return captchaId, err
|
||||
}
|
||||
if captchaId == "" {
|
||||
captchaId = c.GetHeader(key)
|
||||
}
|
||||
return
|
||||
}
|
207
service/lib/cmn/base.go
Normal file
@ -0,0 +1,207 @@
|
||||
package cmn
|
||||
|
||||
import (
|
||||
// "calendar-note-gin/assets"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sun-panel/assets"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// 时间格式
|
||||
|
||||
TimeFormatMode1 = "2006-01-02 15:04:05" // 标准格式
|
||||
TimeFormatMode4 = "2006-01-02 15:04" // 标准格式 无秒
|
||||
TimeFormatMode2 = "Mon Jan 2 15:04:05 -0700 MST 2006"
|
||||
TimeFormatMode3 = "Mon, 2 Jan 2006 15:04:05 -0700 MST" // webdav格式
|
||||
TimeYYYY_mm_dd = "2006-01-02"
|
||||
TIME_MODE_REMINDER_TIME = "200601021504" // 提醒定时器的执行时间格式
|
||||
|
||||
// 随机码字典
|
||||
|
||||
RAND_CODE_MODE1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" // 大写,小写,数字
|
||||
RAND_CODE_MODE2 = "abcdefghijklmnopqrstuvwxyz0123456789" // 小写,数字
|
||||
RAND_CODE_MODE3 = "0123456789" // 数字
|
||||
)
|
||||
|
||||
type Version_Info struct {
|
||||
Version string
|
||||
Version_code int
|
||||
}
|
||||
|
||||
func GetTime() string {
|
||||
return time.Unix(time.Now().Unix(), 0).Format(TimeFormatMode1)
|
||||
}
|
||||
|
||||
// 字符串转时间
|
||||
func StrToTime(timeMode, formatTimeStr string) (t time.Time, err error) {
|
||||
loc, err := time.LoadLocation("Local")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t, err = time.ParseInLocation(timeMode, formatTimeStr, loc) //使用模板在对应时区转化为time.time类型
|
||||
return
|
||||
}
|
||||
|
||||
// md5获取
|
||||
func Md5(str string) string {
|
||||
md5Byte := md5.Sum([]byte(str))
|
||||
return hex.EncodeToString(md5Byte[:])
|
||||
}
|
||||
|
||||
func RandNum(n int) int {
|
||||
rand.Seed(time.Now().Unix())
|
||||
return rand.Intn(n)
|
||||
}
|
||||
|
||||
// 随机生成编码
|
||||
// 随机码字典内容 参考常量 RAND_CODE_MODE*
|
||||
func BuildRandCode(count int, secret_content string) (code string) {
|
||||
return BuildRandCodeBySeed(count, secret_content, time.Now().UnixNano()+int64(rand.Intn(100)))
|
||||
}
|
||||
|
||||
// 随机生成编码 参考常量 RAND_CODE_MODE*
|
||||
func BuildRandCodeBySeed(count int, secret_content string, seed int64) (code string) {
|
||||
// 获取纳秒作为随机数种子
|
||||
rand.Seed(seed)
|
||||
if secret_content == "" {
|
||||
secret_content = RAND_CODE_MODE1
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
code += string(secret_content[rand.Intn(len(secret_content))])
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func InSlice(items []string, item string) bool {
|
||||
for _, eachItem := range items {
|
||||
if eachItem == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 字符串转int
|
||||
func StrToInt(str string) int {
|
||||
intStr, _ := strconv.Atoi(str)
|
||||
return intStr
|
||||
}
|
||||
|
||||
// uint 转string
|
||||
func UintToStr(c uint) string {
|
||||
return strconv.FormatUint(uint64(c), 10)
|
||||
}
|
||||
|
||||
// uint 转string
|
||||
func StrToUint(s string) uint {
|
||||
// i, _ := strconv.Atoi(s)
|
||||
u, _ := strconv.ParseUint(s, 10, 64)
|
||||
return uint(u)
|
||||
}
|
||||
|
||||
// 获取系统信息
|
||||
func GetSysVersionInfo() Version_Info {
|
||||
cBytes, _ := assets.Asset("assets/version")
|
||||
c := string(cBytes)
|
||||
info := strings.Split(c, "|")
|
||||
|
||||
return Version_Info{
|
||||
Version_code: StrToInt(info[0]),
|
||||
Version: info[1],
|
||||
}
|
||||
}
|
||||
|
||||
// 文件是否存在
|
||||
func PathExists(path string) (bool, error) {
|
||||
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 截取字符串,支持多字节字符
|
||||
// start:起始下标,负数从从尾部开始,最后一个为-1
|
||||
// length:截取长度,负数表示截取到末尾
|
||||
func SubRuneStr(str string, start int, length int) (result string) {
|
||||
s := []rune(str)
|
||||
total := len(s)
|
||||
if total == 0 {
|
||||
return
|
||||
}
|
||||
// 允许从尾部开始计算
|
||||
if start < 0 {
|
||||
start = total + start
|
||||
if start < 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
if start > total {
|
||||
return
|
||||
}
|
||||
// 到末尾
|
||||
if length < 0 {
|
||||
length = total
|
||||
}
|
||||
|
||||
end := start + length
|
||||
if end > total {
|
||||
result = string(s[start:])
|
||||
} else {
|
||||
result = string(s[start:end])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 字符串长度
|
||||
func RuneStrLen(str string) int {
|
||||
return len([]rune(str))
|
||||
}
|
||||
|
||||
// 是否在数组中
|
||||
func InStringArray(arr []string, item string) bool {
|
||||
for _, v := range arr {
|
||||
if v == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 从Assets文件夹中抽取文件保存到路劲
|
||||
// AssetsTakeFileToPath("config.ini", targetPath string)
|
||||
func AssetsTakeFileToPath(assetsPath, targetPath string) error {
|
||||
bytes, _ := assets.Asset("assets/" + assetsPath)
|
||||
targetPathPath := path.Dir(targetPath)
|
||||
exists, err := PathExists(targetPathPath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
if err := os.MkdirAll(targetPathPath, 0777); err != nil {
|
||||
fmt.Println(456)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return ioutil.WriteFile(targetPath, bytes, 0666)
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
func PasswordEncryption(password string) string {
|
||||
return Md5(Md5(Md5(password)))
|
||||
}
|
241
service/lib/cmn/log.go
Normal file
@ -0,0 +1,241 @@
|
||||
package cmn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type LogStruct struct {
|
||||
Writer io.Writer
|
||||
File *os.File
|
||||
Print_cfg bool // 此条打印到控制台
|
||||
Separator string
|
||||
}
|
||||
|
||||
type LogFileld map[string]string
|
||||
|
||||
var (
|
||||
LOG_DEBUG = "Debug"
|
||||
LOG_ERROR = "Error"
|
||||
LOG_Info = "Info"
|
||||
LOG_WARNING = "Warning"
|
||||
)
|
||||
|
||||
// 日志颜色
|
||||
var colors = map[string]func(a ...interface{}) string{
|
||||
"Warning": color.New(color.FgYellow).Add(color.Bold).SprintFunc(),
|
||||
"Panic": color.New(color.BgRed).Add(color.Bold).SprintFunc(),
|
||||
"Error": color.New(color.FgRed).Add(color.Bold).SprintFunc(),
|
||||
"Info": color.New(color.FgCyan).Add(color.Bold).SprintFunc(),
|
||||
"Debug": color.New(color.FgWhite).Add(color.Bold).SprintFunc(),
|
||||
}
|
||||
|
||||
// 不同级别前缀与时间的间隔,保持宽度一致
|
||||
var spaces = map[string]string{
|
||||
"Warning": "",
|
||||
"Panic": " ",
|
||||
"Error": " ",
|
||||
"Info": " ",
|
||||
"Debug": " ",
|
||||
}
|
||||
|
||||
// 运行日志静态类
|
||||
var runLogStatic = LogStruct{}
|
||||
|
||||
// 控制台打印
|
||||
// Warning Panic Error Info Debug
|
||||
func Pln(prefix string, msg string) {
|
||||
fmt.Printf(
|
||||
"%s%s %s %s\n",
|
||||
colors[prefix]("["+prefix+"]"),
|
||||
spaces[prefix],
|
||||
time.Now().Format(TimeFormatMode1),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// 控制台打印,支持颜色
|
||||
func Print(color, key, msg string) {
|
||||
fmt.Printf(
|
||||
"%s%s %s\n",
|
||||
colors[color](key),
|
||||
time.Now().Format(TimeFormatMode1),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// func Debug(a ...interface{}) {
|
||||
// fmt.Print("[Debug] ")
|
||||
// fmt.Println(a...)
|
||||
// }
|
||||
|
||||
// // 错误并退出
|
||||
// func ErrorExit(err_title, err_msg string) {
|
||||
// newLog := NewLog("err.log")
|
||||
|
||||
// Pln("Error", err_title+err_msg)
|
||||
// newLog.Error(err_title, err_msg)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
|
||||
// 写入日志的文件
|
||||
func NewLog(log_file_name string) *LogStruct {
|
||||
logStruct := &LogStruct{}
|
||||
logStruct.Separator = ""
|
||||
logDir := path.Dir(log_file_name)
|
||||
ok, _ := PathExists(logDir)
|
||||
if !ok {
|
||||
if err := os.MkdirAll(logDir, 0666); err != nil {
|
||||
fmt.Println("创建日志文件错误", err.Error())
|
||||
}
|
||||
}
|
||||
_, err := os.Stat(log_file_name)
|
||||
if err != nil {
|
||||
f, _ := os.Create(log_file_name)
|
||||
logStruct.File = f
|
||||
logStruct.Writer = io.MultiWriter(f)
|
||||
} else {
|
||||
f, _ := os.OpenFile(log_file_name, os.O_APPEND|os.O_WRONLY, 0666)
|
||||
logStruct.File = f
|
||||
logStruct.Writer = io.MultiWriter(f)
|
||||
}
|
||||
return logStruct
|
||||
}
|
||||
|
||||
// 运行日志直接静态
|
||||
func RunLog() *LogStruct {
|
||||
// 按小时/日/月/年
|
||||
// 先判断文件(夹)是否存在。否多级创建
|
||||
log_file := "res/runtime/log/"
|
||||
ok, _ := PathExists(log_file)
|
||||
if !ok {
|
||||
os.MkdirAll(log_file, 0777)
|
||||
}
|
||||
log_file_name := log_file + time.Unix(time.Now().Unix(), 1).Format("2006-01-02") + ".log"
|
||||
_, err := os.Stat(log_file_name)
|
||||
runLogStatic.Separator = "|"
|
||||
if err != nil {
|
||||
f, _ := os.Create(log_file_name)
|
||||
runLogStatic.File = f
|
||||
runLogStatic.Writer = io.MultiWriter(f)
|
||||
} else {
|
||||
if runLogStatic.File == nil {
|
||||
f, _ := os.OpenFile(log_file_name, os.O_APPEND|os.O_WRONLY, 0666)
|
||||
runLogStatic.File = f
|
||||
runLogStatic.Writer = io.MultiWriter(f)
|
||||
}
|
||||
}
|
||||
return &runLogStatic
|
||||
}
|
||||
|
||||
func (t *LogStruct) Write(content string) (n int, err error) {
|
||||
|
||||
return io.WriteString(t.Writer, content)
|
||||
}
|
||||
|
||||
func (t *LogStruct) Format(log_type string, content string) (n int, err error) {
|
||||
content = log_type + spaces[log_type] + " " + GetTime() + " " + content + "\n"
|
||||
return t.Write(content)
|
||||
}
|
||||
|
||||
func (t *LogStruct) Info(content ...string) (n int, err error) {
|
||||
str := ""
|
||||
for i := 0; i < len(content); i++ {
|
||||
if i != 0 {
|
||||
str += t.Separator + content[i]
|
||||
} else {
|
||||
str += content[i]
|
||||
}
|
||||
}
|
||||
n, err = t.Format("Info", str)
|
||||
if t.Print_cfg == true {
|
||||
Pln("Info", str)
|
||||
t.Print_cfg = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *LogStruct) Debug(content string) {
|
||||
t.Format("Debug", content)
|
||||
if t.Print_cfg == true {
|
||||
Pln("Debug", content)
|
||||
t.Print_cfg = false
|
||||
}
|
||||
}
|
||||
|
||||
func (t *LogStruct) Error(content ...string) {
|
||||
content_str := ""
|
||||
for i := 0; i < len(content); i++ {
|
||||
if i != 0 {
|
||||
content_str += t.Separator + content[i]
|
||||
} else {
|
||||
content_str += content[i]
|
||||
}
|
||||
}
|
||||
t.Format("Error", content_str)
|
||||
if t.Print_cfg == true {
|
||||
Pln("Error", content_str)
|
||||
t.Print_cfg = false
|
||||
}
|
||||
}
|
||||
|
||||
// // 打印错误
|
||||
// func (t *LogStruct) ErrorPrint(key, value string) {
|
||||
// t.Print_cfg = true
|
||||
// t.Error(key, value)
|
||||
// }
|
||||
|
||||
// // 打印Debug
|
||||
// func (t *LogStruct) DebugPrint(key, value string) {
|
||||
// t.Print_cfg = true
|
||||
// content := key + " " + value
|
||||
// t.Debug(content)
|
||||
// }
|
||||
|
||||
// func (t *LogStruct) Print() *LogStruct {
|
||||
// t.Print_cfg = true
|
||||
// return t
|
||||
// }
|
||||
|
||||
// func (t *LogStruct) FormatFileld(field LogFileld) string {
|
||||
// str := ""
|
||||
// for k, v := range field {
|
||||
// str += k + ":\"" + v + "\"" + t.Separator
|
||||
// }
|
||||
// if len(str) != 0 {
|
||||
// str = str[0 : len(str)-1]
|
||||
// }
|
||||
// return str
|
||||
// }
|
||||
|
||||
// TODO(GgoCoder) 日志轮转
|
||||
func InitLogger(fileName string, level zapcore.LevelEnabler) *zap.SugaredLogger {
|
||||
fileWriteSyncer := getLogWriter(fileName)
|
||||
encoder := getEncoder()
|
||||
core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(fileWriteSyncer, zapcore.AddSync(os.Stdout)), level)
|
||||
logger := zap.New(core, zap.AddCaller())
|
||||
return logger.Sugar()
|
||||
}
|
||||
|
||||
func getEncoder() zapcore.Encoder {
|
||||
logConf := zap.NewProductionEncoderConfig()
|
||||
logConf.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
logConf.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
return zapcore.NewConsoleEncoder(logConf)
|
||||
}
|
||||
|
||||
func getLogWriter(fileName string) zapcore.WriteSyncer {
|
||||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
log.Panic("failed to create log file", fileName)
|
||||
}
|
||||
return zapcore.AddSync(file)
|
||||
}
|
99
service/lib/cmn/systemSetting/systemSetting.go
Normal file
@ -0,0 +1,99 @@
|
||||
package systemSetting
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sun-panel/lib/cache"
|
||||
"sun-panel/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
SYSTEM_APPLICATION = "system_application"
|
||||
SYSTEM_EMAIL = "system_email"
|
||||
DISCLAIMER = "disclaimer" // 免责声明 储存类型:字符串
|
||||
WEB_ABOUT_DESCRIPTION = "web_about_description" // 关于的描述信息
|
||||
)
|
||||
|
||||
type SystemSettingCache struct {
|
||||
Cache cache.Cacher[interface{}]
|
||||
}
|
||||
|
||||
type Email struct {
|
||||
Host string `json:"host" binding:"required"`
|
||||
Port int `json:"port" binding:"required"`
|
||||
Mail string `json:"mail" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type Register struct {
|
||||
EmailSuffix string `json:"emailSuffix"` // 注册邮箱后缀
|
||||
OpenRegister bool `json:"openRegister"` // 开放注册
|
||||
}
|
||||
|
||||
type Login struct {
|
||||
LoginCaptcha bool `json:"loginCaptcha"` // 登录验证码
|
||||
}
|
||||
|
||||
type ApplicationSetting struct {
|
||||
Register
|
||||
Login
|
||||
WebSiteUrl string `json:"webSiteUrl"` // 站点地址
|
||||
}
|
||||
|
||||
var (
|
||||
ErrorNoExists = errors.New("no exists")
|
||||
)
|
||||
|
||||
// 系统配置启用缓存功能
|
||||
func (s *SystemSettingCache) GetValueString(configName string) (result string, err error) {
|
||||
if v, ok := s.Cache.Get(configName); ok {
|
||||
if v1, ok1 := v.(string); ok1 {
|
||||
// fmt.Println("读取缓存")
|
||||
return v1, nil
|
||||
}
|
||||
}
|
||||
|
||||
mSetting := models.SystemSetting{}
|
||||
result, err = mSetting.Get(configName)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
err = ErrorNoExists
|
||||
}
|
||||
// 查询出来,缓存起来
|
||||
s.Cache.SetDefault(configName, result)
|
||||
return
|
||||
}
|
||||
|
||||
// value 需为指针
|
||||
func (s *SystemSettingCache) GetValueByInterface(configName string, value interface{}) error {
|
||||
if v, ok := s.Cache.Get(configName); ok {
|
||||
// fmt.Println("缓存")
|
||||
if s, sok := v.(string); sok {
|
||||
if err := json.Unmarshal([]byte(s), value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
mSetting := models.SystemSetting{}
|
||||
result, err := mSetting.Get(configName)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
err = ErrorNoExists
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal([]byte(result), value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Cache.SetDefault(configName, result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SystemSettingCache) Set(configName string, configValue interface{}) error {
|
||||
s.Cache.Delete(configName)
|
||||
mSetting := models.SystemSetting{}
|
||||
err := mSetting.Set(configName, configValue)
|
||||
return err
|
||||
}
|
20
service/lib/cmn/verifyFormat.go
Normal file
@ -0,0 +1,20 @@
|
||||
package cmn
|
||||
|
||||
import "regexp"
|
||||
|
||||
// 公式
|
||||
const (
|
||||
VERIFY_EXP_USERNAME = `^[a-zA-Z0-9_\.\@]{5,80}$`
|
||||
VERIFY_EXP_PASSWORD = `^[a-zA-Z0-9_\.\&\@]{6,16}$`
|
||||
)
|
||||
|
||||
// 正则验证
|
||||
func VerifyFormat(exp, str string) bool {
|
||||
reg := regexp.MustCompile(exp)
|
||||
return reg.MatchString(str)
|
||||
}
|
||||
|
||||
// 验证邮箱
|
||||
func VerifyEmail(email string) bool {
|
||||
return VerifyFormat(`\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`, email)
|
||||
}
|
92
service/lib/computerInfo/base.go
Normal file
@ -0,0 +1,92 @@
|
||||
package computerInfo
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"gitlab.com/tingshuo/go-diskstate/diskstate"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
Name string
|
||||
FileSystem string
|
||||
Total uint64
|
||||
Free uint64
|
||||
}
|
||||
|
||||
type storageInfo struct {
|
||||
Name string
|
||||
Size uint64
|
||||
FreeSpace uint64
|
||||
FileSystem string
|
||||
Used uint64
|
||||
}
|
||||
|
||||
// func GetStorageInfo() {
|
||||
// var storageinfo []storageInfo
|
||||
// var loaclStorages []Storage
|
||||
// err := wmi.Query("Select * from Win32_LogicalDisk", &storageinfo)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// for _, storage := range storageinfo {
|
||||
// info := Storage{
|
||||
// Name: storage.Name,
|
||||
// FileSystem: storage.FileSystem,
|
||||
// Total: storage.Size / 1024 / 1024 / 1024,
|
||||
// Free: storage.FreeSpace / 1024 / 1024 / 1024,
|
||||
// }
|
||||
// if info.Total >= 1 {
|
||||
// fmt.Printf("%s总大小%dG,可用%dG\n", info.Name, info.Total, info.Free)
|
||||
// loaclStorages = append(loaclStorages, info)
|
||||
// }
|
||||
// }
|
||||
// fmt.Printf("localStorages:= %v\n", loaclStorages)
|
||||
// }
|
||||
|
||||
func GetCurrentStorageInfo(path string) storageInfo {
|
||||
state := diskstate.DiskUsage(path)
|
||||
info := storageInfo{}
|
||||
info.Size = uint64(state.All / diskstate.B)
|
||||
info.FreeSpace = uint64(state.Free / diskstate.B)
|
||||
info.Used = uint64(state.Used / diskstate.B)
|
||||
|
||||
// fmt.Printf("All=%dM, Free=%dM, Available=%dM, Used=%dM, Usage=%d%%",
|
||||
// state.All/diskstate.B, state.Free/diskstate.MB, state.Available/diskstate.MB, state.Used/diskstate.MB, 100*state.Used/state.All)
|
||||
return info
|
||||
}
|
||||
|
||||
type ComputerMonitor struct {
|
||||
CPU float64 `json:"cpu"`
|
||||
Mem float64 `json:"mem"`
|
||||
}
|
||||
|
||||
// GetCPUPercent 获取CPU使用率
|
||||
func GetCPUPercent() float64 {
|
||||
percent, err := cpu.Percent(time.Second, false)
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
return -1
|
||||
}
|
||||
return percent[0]
|
||||
}
|
||||
|
||||
// GetMemPercent 获取内存使用率
|
||||
func GetMemPercent() float64 {
|
||||
memInfo, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
return -1
|
||||
}
|
||||
return memInfo.UsedPercent
|
||||
}
|
||||
|
||||
func GetCpuMem() ComputerMonitor {
|
||||
var res ComputerMonitor
|
||||
res.CPU = GetCPUPercent()
|
||||
res.Mem = GetMemPercent()
|
||||
return res
|
||||
}
|
73
service/lib/iniConfig/iniconfig.go
Normal file
@ -0,0 +1,73 @@
|
||||
package iniConfig
|
||||
|
||||
import (
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
type IniConfig struct {
|
||||
Err error
|
||||
Config *ini.File
|
||||
Default map[string]map[string]string // 默认配置
|
||||
FileName string
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
func (t *IniConfig) GetValue(section string, name string) *ini.Key {
|
||||
return t.Config.Section(section).Key(name)
|
||||
}
|
||||
|
||||
// 设置配置
|
||||
func (t *IniConfig) SetValue(section string, name string, value string) error {
|
||||
t.Config.Section(section).Key(name).SetValue(value)
|
||||
return t.Config.SaveTo(t.FileName)
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
func (t *IniConfig) GetValueString(section string, name string) string {
|
||||
return t.Config.Section(section).Key(name).String()
|
||||
}
|
||||
|
||||
// 获取字符串配置,如果没有会查找默认值
|
||||
func (t *IniConfig) GetValueStringOrDefault(section string, name string) string {
|
||||
value := t.GetValueString(section, name)
|
||||
if value == "" && t.Default[section] != nil && t.Default[section][name] != "" {
|
||||
return t.Default[section][name]
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
func (t *IniConfig) GetValueInt(section string, name string) int {
|
||||
return t.Config.Section(section).Key(name).MustInt()
|
||||
}
|
||||
|
||||
// 获取组配置
|
||||
func (t *IniConfig) GetSection(section string, result interface{}) error {
|
||||
if group, err := t.Config.GetSection(section); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if err := group.MapTo(result); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除组
|
||||
func (t *IniConfig) DeleteSection(section string) {
|
||||
t.Config.DeleteSection(section)
|
||||
t.Config.SaveTo(t.FileName)
|
||||
}
|
||||
|
||||
// 创建一个配置对象
|
||||
func NewIniConfig(filename string) *IniConfig {
|
||||
config, err := ini.Load(filename)
|
||||
|
||||
return &IniConfig{
|
||||
Err: err,
|
||||
Config: config,
|
||||
FileName: filename,
|
||||
}
|
||||
}
|
21
service/lib/jsonConfig/SpecialDayModel.go
Normal file
@ -0,0 +1,21 @@
|
||||
package jsonConfig
|
||||
|
||||
type SpecialDayModel struct {
|
||||
ConfigModel
|
||||
Data SpecialDayDataModel
|
||||
}
|
||||
|
||||
type SpecialDayDataModel struct {
|
||||
OnlyId string `json:"onlyId"` // 唯一ID
|
||||
Name string `json:"name"` // 名称
|
||||
UpdateTime string `json:"updateTime"` // 更新时间
|
||||
// Year int `json:"year"` // 年份
|
||||
StartDate string `json:"startDate"` // 开始日期
|
||||
EndDate string `json:"endDate"` // 结束日期
|
||||
Days map[string]SpecialDayDataDaysModel `json:"days"` // 天数据
|
||||
}
|
||||
|
||||
type SpecialDayDataDaysModel struct {
|
||||
Holiday bool `json:"holiday"` // 唯一ID
|
||||
Name string `json:"name"` // 名称
|
||||
}
|
19
service/lib/jsonConfig/abilityModel.go
Normal file
@ -0,0 +1,19 @@
|
||||
package jsonConfig
|
||||
|
||||
// 事件风格数据
|
||||
type EventStyleDataModel struct {
|
||||
Title string `json:"title"` // 标题
|
||||
ClassName string `json:"className"` // 类名称
|
||||
TextColor string `json:"textColor"` // 字体颜色
|
||||
BackgroundColor string `json:"backgroundColor"` // 背景颜色
|
||||
BorderColor string `json:"borderColor"` // 边框颜色
|
||||
}
|
||||
|
||||
type EventStyleModel struct {
|
||||
ConfigModel
|
||||
Data []EventStyleDataModel
|
||||
}
|
||||
|
||||
func (e *EventStyleModel) GetImportData() error {
|
||||
return nil
|
||||
}
|
101
service/lib/jsonConfig/configModel.go
Normal file
@ -0,0 +1,101 @@
|
||||
package jsonConfig
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type JsonConfiger interface {
|
||||
GetImportData() error
|
||||
// ExportFile()
|
||||
}
|
||||
|
||||
type ConfigModel struct {
|
||||
AppName string `json:"AppName"`
|
||||
Ability string `json:"Ability"`
|
||||
Version string `json:"Version"`
|
||||
AbilityVersion string `json:"AbilityVersion"`
|
||||
AppAllowLowVersion string `json:"AppAllowLowVersion"`
|
||||
Data interface{} `json:"Data"`
|
||||
}
|
||||
|
||||
var expoprtSuffix = ".lcn.json"
|
||||
|
||||
const (
|
||||
ABILITY_MODE_EVENT_STYLE = "EventStyle" // 时间风格
|
||||
ABILITY_MODE_SPECIAL_DAY = "SpecialDay" // 特殊的日期
|
||||
)
|
||||
|
||||
// 生成输出文件
|
||||
func BuildExportFile(cfgModel *ConfigModel) ([]byte, error) {
|
||||
content, err := json.Marshal(cfgModel)
|
||||
return content, err
|
||||
}
|
||||
|
||||
func Write(ctx *gin.Context, fileName string, content []byte) {
|
||||
ctx.Writer.Header().Add("Content-Type", "application/octet-stream")
|
||||
ctx.Writer.Header().Add("Content-disposition", "attachment;filename="+fileName+expoprtSuffix)
|
||||
ctx.Writer.Header().Add("Content-Transfer-Encoding", "binary")
|
||||
ctx.Writer.Write(content)
|
||||
}
|
||||
|
||||
func GetImportData(JsonConfiger) {
|
||||
|
||||
}
|
||||
|
||||
func NewConfigModel(ability, abilityVersion string) *ConfigModel {
|
||||
return &ConfigModel{
|
||||
AppName: "Li-Calendar",
|
||||
Version: "1",
|
||||
AppAllowLowVersion: "1",
|
||||
Ability: ability,
|
||||
AbilityVersion: abilityVersion,
|
||||
}
|
||||
}
|
||||
|
||||
// 验证配置模型数据是否相同
|
||||
func ConfigModelCheck(data *ConfigModel, ability, abilityVersion string) bool {
|
||||
newData := NewConfigModel(ability, abilityVersion)
|
||||
if *data != *newData {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// func InportConfigFile(f multipart.FileHeader, eventStyle EventStyleModel) (EventStyleModel, error) {
|
||||
|
||||
// src, err := f.Open()
|
||||
// defer src.Close()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// contentByte, err := ioutil.ReadAll(src)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// configFile := ConfigModel{}
|
||||
// if err := json.Unmarshal(contentByte, &configFile); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// v, ok := configFile.Data.(EventStyleModel)
|
||||
// return errors.New("格式")
|
||||
// if !ok {
|
||||
// return errors.New("格式错误")
|
||||
// }
|
||||
|
||||
// if err := json.Unmarshal(contentByte, &configFile); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||
// fileName := cmn.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||
// fildDir := fmt.Sprintf("%s/%d/%d/%d/", configUpload, time.Now().Year(), time.Now().Month(), time.Now().Day())
|
||||
// isExist, _ := cmn.PathExists(fildDir)
|
||||
// if !isExist {
|
||||
// os.MkdirAll(fildDir, os.ModePerm)
|
||||
// }
|
||||
// filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||
|
||||
// }
|
60
service/lib/language/lang.go
Normal file
@ -0,0 +1,60 @@
|
||||
package language
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"sun-panel/lib/cmn"
|
||||
"sun-panel/lib/iniConfig"
|
||||
)
|
||||
|
||||
type LangStructObj struct {
|
||||
LangContet *iniConfig.IniConfig
|
||||
}
|
||||
|
||||
func NewLang(langPath string) *LangStructObj {
|
||||
langObj := LangStructObj{}
|
||||
exists, _ := cmn.PathExists(langPath)
|
||||
|
||||
if exists {
|
||||
langObj.LangContet = iniConfig.NewIniConfig(langPath) // 读取配置
|
||||
} else {
|
||||
cmn.Pln(cmn.LOG_ERROR, "language file does not exist:"+langPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
return &langObj
|
||||
}
|
||||
|
||||
// 获取
|
||||
// common.lang
|
||||
// 配置文件格式
|
||||
// [common]
|
||||
// lang=zh-cn
|
||||
func (l *LangStructObj) Get(key string) string {
|
||||
if key == "" {
|
||||
return key
|
||||
}
|
||||
keyArr := strings.Split(key, ".")
|
||||
if len(keyArr) < 2 {
|
||||
return l.LangContet.GetValueString(keyArr[0], "NOT EMPTY")
|
||||
} else {
|
||||
return l.LangContet.GetValueString(keyArr[0], keyArr[1])
|
||||
}
|
||||
}
|
||||
|
||||
// 获取并替换字段
|
||||
func (l *LangStructObj) GetWithFields(key string, fields map[string]string) string {
|
||||
c := l.Get(key)
|
||||
for k, v := range fields {
|
||||
c = strings.ReplaceAll(c, `{`+k+`}`, v)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// 获取值并向后追加
|
||||
func (l *LangStructObj) GetAndInsert(key string, insertContent ...string) string {
|
||||
content := l.Get(key) + " "
|
||||
for _, v := range insertContent {
|
||||
content += v
|
||||
}
|
||||
return content
|
||||
}
|
141
service/lib/mail/email.go
Normal file
@ -0,0 +1,141 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"sun-panel/global"
|
||||
"sun-panel/models"
|
||||
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
type EmailInfo struct {
|
||||
Username string // 账号
|
||||
Password string // 密码
|
||||
Host string // 服务器地址
|
||||
Port int // 端口 默认465
|
||||
}
|
||||
|
||||
type Emailer struct {
|
||||
EmailInfo EmailInfo
|
||||
Dialer *gomail.Dialer
|
||||
}
|
||||
|
||||
func NewEmailer(emailInfo EmailInfo) *Emailer {
|
||||
dialer := gomail.NewDialer(emailInfo.Host, emailInfo.Port, emailInfo.Username, emailInfo.Password)
|
||||
return &Emailer{
|
||||
Dialer: dialer,
|
||||
EmailInfo: emailInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Emailer) Send(mailTo []string, send_name, title, body string) error {
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", e.EmailInfo.Username)
|
||||
//这种方式可以添加别名,即“XX官方”
|
||||
//说明:如果是用网易邮箱账号发送,以下方法别名可以是中文,如果是qq企业邮箱,以下方法用中文别名,会报错,需要用上面此方法转码
|
||||
//m.SetHeader("From", "FB Sample"+"<"+mailConn["user"]+">") //这种方式可以添加别名,即“FB Sample”, 也可以直接用<code>m.SetHeader("From",mailConn["user"])</code> 读者可以自行实验下效果
|
||||
//m.SetHeader("From", mailConn["user"])
|
||||
m.SetHeader("To", mailTo...) //发送给多个用户
|
||||
m.SetHeader("Subject", title) //设置邮件主题
|
||||
m.SetBody("text/html", body) //设置邮件正文
|
||||
|
||||
return e.Dialer.DialAndSend(m)
|
||||
}
|
||||
|
||||
// 发送邮件
|
||||
func (m *Emailer) SendMail(mailTo string, title, content string) error {
|
||||
fromUrl := "127.0.0.1"
|
||||
appName := global.Lang.Get("common.app_name")
|
||||
mUser := models.User{Mail: mailTo}
|
||||
userInfo := mUser.GetUserInfoByMail()
|
||||
|
||||
nickName := ""
|
||||
if userInfo != nil && userInfo.Name != "" {
|
||||
nickName = " " + userInfo.Name
|
||||
}
|
||||
|
||||
body := `<meta charset="utf-8">
|
||||
<table width="600px" style="max-width: 600px;" align="center">
|
||||
<tr style="width: 600px;background-color: rgb(28, 197, 249);">
|
||||
<td align="left" style="width: 600px;padding: 22px 18px 11px;display: inline-block;">
|
||||
<div style="font-weight: 900;font-size: 18px;">
|
||||
<p>Hi` + nickName + `:</p>
|
||||
</div>
|
||||
</td>
|
||||
<td style="width: 100%;display: inline-block;border-top: 4px dashed rgb(255, 255, 255);"> </td>
|
||||
<td style="width: 600px;padding: 18px;display: inline-block;">
|
||||
<div align="left" style="color: rgb(57, 57, 57); line-height: 1.6; font-size: 14px; margin: 0px;font-weight: bolder;">
|
||||
` + content + `
|
||||
</div>
|
||||
</td>
|
||||
<td style="width: 600px;padding: 18px;display: inline-block;">
|
||||
<div align="rignt">
|
||||
<div style="font-size: 14px; margin: 0px;text-align: right;font-size: 14px; font-weight: bolder;">
|
||||
-- ` + global.Lang.Get("mail.from") + `[<a href="` + fromUrl + `" style="color: #575757;">` + appName + `</a>]</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>`
|
||||
return SendMail(m, []string{mailTo}, appName, title, body)
|
||||
}
|
||||
|
||||
// 发送链接邮件
|
||||
func (m *Emailer) SendMailOfLink(mailTo, title, content, btn_name, url string) error {
|
||||
content = content + getLabelHtmlBtn(btn_name, url)
|
||||
return m.SendMail(mailTo, title, content)
|
||||
}
|
||||
|
||||
// 发送注册邮件
|
||||
func (m *Emailer) SendMailOfRegister(mailTo, key string) error {
|
||||
fromUrl := "127.0.0.1"
|
||||
appName := global.Lang.Get("common.app_name")
|
||||
title := global.Lang.GetWithFields("mail.register_title", map[string]string{
|
||||
"AppName": appName,
|
||||
})
|
||||
content := global.Lang.GetWithFields("mail.register_content", map[string]string{
|
||||
"AppName": appName,
|
||||
})
|
||||
return m.SendMailOfLink(mailTo, title, content, global.Lang.Get("mail.register_click_btn"), fromUrl+"/profile/auth.html#/linkRegister?code="+key)
|
||||
}
|
||||
|
||||
// 发送验证码邮件
|
||||
func (m *Emailer) SendMailOfVCode(mailTo, title, content, vCode string) error {
|
||||
// appName := global.Lang.Get("common.app_name")
|
||||
content = content + getLabelHtmlVcode(vCode)
|
||||
return m.SendMail(mailTo, title, content)
|
||||
}
|
||||
|
||||
// 发送邮件
|
||||
//
|
||||
// @param emailer
|
||||
// @param mailTo
|
||||
// @param send_name
|
||||
// @param title
|
||||
// @param body
|
||||
// @return error
|
||||
func SendMail(emailer *Emailer, mailTo []string, send_name, title, body string) error {
|
||||
//定义邮箱服务器连接信息,如果是网易邮箱 pass填密码,qq邮箱填授权码
|
||||
if emailer.EmailInfo.Port == 0 {
|
||||
emailer.EmailInfo.Port = 465
|
||||
}
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", m.FormatAddress(emailer.EmailInfo.Username, send_name))
|
||||
//这种方式可以添加别名,即“XX官方”
|
||||
//说明:如果是用网易邮箱账号发送,以下方法别名可以是中文,如果是qq企业邮箱,以下方法用中文别名,会报错,需要用上面此方法转码
|
||||
//m.SetHeader("From", "FB Sample"+"<"+mailConn["user"]+">") //这种方式可以添加别名,即“FB Sample”, 也可以直接用<code>m.SetHeader("From",mailConn["user"])</code> 读者可以自行实验下效果
|
||||
//m.SetHeader("From", mailConn["user"])
|
||||
m.SetHeader("To", mailTo...) //发送给多个用户
|
||||
m.SetHeader("Subject", title) //设置邮件主题
|
||||
m.SetBody("text/html", body) //设置邮件正文
|
||||
|
||||
// d.TLSConfig = &tls.Config{InsecureSkipVerify: true} // 跳过证书验证,不推荐
|
||||
err := emailer.Dialer.DialAndSend(m)
|
||||
return err
|
||||
}
|
||||
|
||||
func getLabelHtmlBtn(btn_name string, href string) string {
|
||||
return `<div><a style="color: #fff;background-color: #2e2e2e;display: inline-block;padding: 10px 30px;border-radius: 5px;" href="` + href + `">` + btn_name + `</a></div>`
|
||||
}
|
||||
|
||||
func getLabelHtmlVcode(vcode string) string {
|
||||
return `<p><div style="color: #fff;background-color: #2e2e2e;display: inline-block;padding: 10px 30px;border-radius: 5px;">` + vcode + `</div></p>`
|
||||
}
|
72
service/lib/mail/mail.go
Normal file
@ -0,0 +1,72 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"sun-panel/global"
|
||||
)
|
||||
|
||||
// 发送注册验证码
|
||||
//
|
||||
// @param emailer
|
||||
// @param mailTo 收件人
|
||||
// @param vcode 验证码
|
||||
// @return error
|
||||
func SendRegisterEmail(emailer *Emailer, mailTo, vcode string) error {
|
||||
appName := global.Lang.Get("common.app_name")
|
||||
title := global.Lang.GetWithFields("mail.register_vcode_title", map[string]string{
|
||||
"AppName": appName,
|
||||
})
|
||||
content := global.Lang.GetWithFields("mail.register_vcode_content", map[string]string{
|
||||
"AppName": appName,
|
||||
"Minute": "10",
|
||||
})
|
||||
err := emailer.SendMailOfVCode(mailTo, title, content, vcode)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("failed to send email to %s, err:%+v\n", mailTo, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 发送重置密码验证码
|
||||
//
|
||||
// @param emailer
|
||||
// @param mailTo
|
||||
// @param vcode
|
||||
// @return error
|
||||
func SendResetPasswordVCode(emailer *Emailer, mailTo, vcode string) error {
|
||||
title := global.Lang.Get("mail.reset_password_password_title")
|
||||
content := global.Lang.Get("mail.reset_password_password_content")
|
||||
err := emailer.SendMailOfVCode(mailTo, title, content, vcode)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("failed to send email to %s, err:%+v\n", mailTo, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// // 事件提醒
|
||||
// //
|
||||
// // @param emailer
|
||||
// // @param mailTo
|
||||
// // @param eventReminder
|
||||
// // @return error
|
||||
// func SendEventReminder(emailer *Emailer, mailTo string, eventReminder models.EventReminder) error {
|
||||
// startTime := eventReminder.Event.StartTime.Time.Format(cmn.TimeFormatMode4)
|
||||
// endTime := eventReminder.Event.EndTime.Time.Format(cmn.TimeFormatMode4)
|
||||
// title := global.Lang.GetWithFields("mail.reminder_title", map[string]string{
|
||||
// "Title": eventReminder.Event.Title,
|
||||
// "Time": startTime,
|
||||
// })
|
||||
// contentParam := map[string]string{
|
||||
// "ItemTitle": eventReminder.Event.Item.Title,
|
||||
// "Time": startTime,
|
||||
// }
|
||||
// content := "<p>" + global.Lang.GetWithFields("mail.reminder_content", contentParam) + "</p>"
|
||||
// content += "<p>" + global.Lang.Get("mail.reminder_event_title") + " : " + eventReminder.Event.Title + "</p>"
|
||||
// content += "<p>" + global.Lang.Get("common.start_time") + " : " + startTime + "</p>"
|
||||
// content += "<p>" + global.Lang.Get("common.end_time") + " : " + endTime + "</p>"
|
||||
|
||||
// err := emailer.SendMail(mailTo, title, content)
|
||||
// if err != nil {
|
||||
// global.Logger.Errorf("failed to send email to %s, err:%+v\n", mailTo, err)
|
||||
// }
|
||||
// return err
|
||||
// }
|
21
service/lib/queue/base.go
Normal file
@ -0,0 +1,21 @@
|
||||
package queue
|
||||
|
||||
// 队列器
|
||||
type Queuer interface {
|
||||
// 左侧插入
|
||||
LPush(value ...interface{}) error
|
||||
// 右侧插入
|
||||
RPush(value ...interface{}) error
|
||||
// 删除元素
|
||||
Delete(value interface{}) error
|
||||
// 使用下标获取值
|
||||
GetByIndex(index int64, v interface{}) error
|
||||
// 左侧读取并删除
|
||||
LPop(v interface{}) error
|
||||
// 右侧读取并删除
|
||||
RPop(v interface{}) error
|
||||
// 队列长度
|
||||
Length() (int64, error)
|
||||
// 清空队列
|
||||
Flush() error
|
||||
}
|
105
service/lib/queue/queueMemory/memory.go
Normal file
@ -0,0 +1,105 @@
|
||||
package queueMemory
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Pool struct {
|
||||
Values [][]byte
|
||||
Lock sync.RWMutex
|
||||
}
|
||||
|
||||
func New() *Pool {
|
||||
return &Pool{}
|
||||
}
|
||||
|
||||
func (k *Pool) LPush(value ...interface{}) error {
|
||||
k.Lock.Lock()
|
||||
defer k.Lock.Unlock()
|
||||
for i := 0; i < len(value); i++ {
|
||||
v, _ := json.Marshal(value[i])
|
||||
k.Values = append([][]byte{v}, k.Values...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Pool) RPush(value ...interface{}) error {
|
||||
k.Lock.Lock()
|
||||
defer k.Lock.Unlock()
|
||||
for i := 0; i < len(value); i++ {
|
||||
v, _ := json.Marshal(value[i])
|
||||
k.Values = append(k.Values, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Pool) Delete(value interface{}) error {
|
||||
k.Lock.Lock()
|
||||
defer k.Lock.Unlock()
|
||||
var index int64
|
||||
v, _ := json.Marshal(value)
|
||||
|
||||
for i, item := range k.Values {
|
||||
if reflect.DeepEqual(item, v) {
|
||||
index = int64(i)
|
||||
k.removeIndex(index)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 取出值
|
||||
func (k *Pool) GetByIndex(index int64, v interface{}) error {
|
||||
k.Lock.RLock()
|
||||
defer k.Lock.RUnlock()
|
||||
if int64(len(k.Values)) >= index {
|
||||
json.Unmarshal(k.Values[index], v)
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("index non-existent")
|
||||
}
|
||||
}
|
||||
|
||||
// 左-取出并删除
|
||||
func (k *Pool) LPop(v interface{}) error {
|
||||
if err := k.GetByIndex(0, v); err != nil {
|
||||
return err
|
||||
} else {
|
||||
k.removeIndex(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 右-取出并删除
|
||||
func (k *Pool) RPop(v interface{}) error {
|
||||
index := int64(len(k.Values) - 1)
|
||||
if err := k.GetByIndex(index, v); err != nil {
|
||||
return err
|
||||
} else {
|
||||
k.removeIndex(index)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Pool) removeIndex(index int64) error {
|
||||
k.Lock.Lock()
|
||||
defer k.Lock.Unlock()
|
||||
k.Values = append(k.Values[:index], k.Values[index+1:]...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Pool) Length() (int64, error) {
|
||||
return int64(len(k.Values)), nil
|
||||
}
|
||||
|
||||
func (k *Pool) Flush() error {
|
||||
k.Lock.Lock()
|
||||
defer k.Lock.Unlock()
|
||||
k.Values = nil
|
||||
return nil
|
||||
}
|
96
service/lib/queue/queueRedis/redis.go
Normal file
@ -0,0 +1,96 @@
|
||||
package queueRedis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type Pool struct {
|
||||
Ctx context.Context
|
||||
Redis *redis.Client
|
||||
Name string
|
||||
}
|
||||
|
||||
func New(redisDb *redis.Client, name string) *Pool {
|
||||
ctx := context.Background()
|
||||
return &Pool{
|
||||
Ctx: ctx,
|
||||
Redis: redisDb,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Pool) LPush(value ...interface{}) error {
|
||||
var values []interface{}
|
||||
for _, v := range value {
|
||||
values = append(values, k.encode(v))
|
||||
}
|
||||
|
||||
return k.Redis.LPush(k.Ctx, k.Name, values...).Err()
|
||||
}
|
||||
|
||||
func (k *Pool) RPush(value ...interface{}) error {
|
||||
var values []interface{}
|
||||
for _, v := range value {
|
||||
values = append(values, k.encode(v))
|
||||
}
|
||||
return k.Redis.RPush(k.Ctx, k.Name, values...).Err()
|
||||
}
|
||||
|
||||
func (k *Pool) Delete(value interface{}) error {
|
||||
return k.Redis.LRem(k.Ctx, k.Name, 0, k.encode(value)).Err()
|
||||
}
|
||||
|
||||
// 取出值
|
||||
func (k *Pool) GetByIndex(index int64, v interface{}) error {
|
||||
if d, err := k.Redis.LIndex(k.Ctx, k.Name, index).Result(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
k.decode(d, v)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 左-取出并删除
|
||||
func (k *Pool) LPop(v interface{}) error {
|
||||
if d, err := k.Redis.LPop(k.Ctx, k.Name).Result(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
k.decode(d, v)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 右-取出并删除
|
||||
func (k *Pool) RPop(v interface{}) error {
|
||||
if d, err := k.Redis.RPop(k.Ctx, k.Name).Result(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
k.decode(d, v)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Pool) encode(value any) string {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func (k *Pool) decode(v string, value interface{}) {
|
||||
err := json.Unmarshal([]byte(v), value)
|
||||
_ = err
|
||||
}
|
||||
|
||||
func (k *Pool) Length() (int64, error) {
|
||||
r := k.Redis.LLen(k.Ctx, k.Name)
|
||||
return r.Result()
|
||||
}
|
||||
|
||||
func (k *Pool) Flush() error {
|
||||
return k.Redis.Del(k.Ctx, k.Name).Err()
|
||||
}
|
10
service/lib/user/user.go
Normal file
@ -0,0 +1,10 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Logout(c *gin.Context) {
|
||||
c.SetCookie("cloud_tk", "", 0, "/source/", "", false, true)
|
||||
|
||||
}
|
21
service/main.go
Normal file
@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sun-panel/global"
|
||||
"sun-panel/initialize"
|
||||
"sun-panel/router"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := initialize.InitApp()
|
||||
if err != nil {
|
||||
log.Println("初始化错误:", err.Error())
|
||||
panic(err)
|
||||
}
|
||||
httpPort := global.Config.GetValueStringOrDefault("base", "http_port")
|
||||
|
||||
if err := router.InitRouters(":" + httpPort); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
68
service/models/SystemSetting.go
Normal file
@ -0,0 +1,68 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 系统设置
|
||||
type SystemSetting struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
ConfigName string `gorm:"type:varchar(50)"`
|
||||
ConfigValue string `gorm:"type:text"`
|
||||
}
|
||||
|
||||
func (m *SystemSetting) Get(configName string) (result string, err error) {
|
||||
var res SystemSetting
|
||||
if err := Db.Model(m).Select("ConfigValue").First(&res, "config_name=?", configName).Error; err != nil {
|
||||
return result, err
|
||||
}
|
||||
result = res.ConfigValue
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *SystemSetting) GetValueByInterface(configName string, structValue interface{}) error {
|
||||
result, err := m.Get(configName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal([]byte(result), structValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 暂时仅支持结构体和字符串
|
||||
func (m *SystemSetting) Set(configName string, configValue interface{}) error {
|
||||
findRes := SystemSetting{}
|
||||
db := Db.Model(m).First(&findRes, "config_name=?", configName)
|
||||
if err := db.Error; err != nil && err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
value := ""
|
||||
if s, ok := configValue.(string); !ok {
|
||||
if jsonStr, err := json.Marshal(configValue); err != nil {
|
||||
value = ""
|
||||
} else {
|
||||
value = string(jsonStr)
|
||||
}
|
||||
} else {
|
||||
value = s
|
||||
}
|
||||
|
||||
if db.RowsAffected == 0 {
|
||||
// 添加
|
||||
if err := Db.Model(m).Create(&SystemSetting{ConfigName: configName, ConfigValue: value}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
// 修改
|
||||
if err := Db.Model(m).Where("id=?", findRes.ID).Update("config_value", value).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|