refactor: 登录页调整

This commit is contained in:
meilin.huang
2023-09-23 22:52:05 +08:00
parent 6681dc1057
commit a1eca3d691
18 changed files with 506 additions and 256 deletions

View File

@@ -15,6 +15,7 @@ import LockScreen from '@/views/layout/lockScreen/index.vue';
import Setings from '@/views/layout/navBars/breadcrumb/setings.vue';
import Watermark from '@/common/utils/wartermark';
import mittBus from '@/common/utils/mitt';
import { getThemeConfig } from './common/utils/storage';
const setingsRef = ref();
const route = useRoute();
@@ -42,10 +43,14 @@ onMounted(() => {
mittBus.on('openSetingsDrawer', () => {
openSetingsDrawer();
});
// 获取缓存中的布局配置
if (getLocal('themeConfig')) {
themeConfigStores.setThemeConfig({ themeConfig: getLocal('themeConfig') });
const tc = getThemeConfig();
if (tc) {
themeConfigStores.setThemeConfig({ themeConfig: tc });
document.documentElement.style.cssText = getLocal('themeConfigStyle');
themeConfigStores.switchDark(tc.isDark);
}
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -1,20 +1,16 @@
import { nextTick } from 'vue';
import loadingCss from '@/theme/loading.scss?inline';
import '@/theme/loading.scss';
// 定义方法
/**
* 页面全局 Loading
* @method start 创建 loading
* @method done 移除 loading
*/
export const NextLoading = {
// 载入 css
setCss: () => {
let link = document.createElement('link');
link.rel = 'stylesheet';
link.href = loadingCss;
link.crossOrigin = 'anonymous';
document.getElementsByTagName('head')[0].appendChild(link);
},
// 创建 loading
start: () => {
const bodys: any = document.body;
const div = document.createElement('div');
const bodys: Element = document.body;
const div = <HTMLElement>document.createElement('div');
div.setAttribute('class', 'loading-next');
const htmls = `
<div class="loading-next-box">
@@ -35,12 +31,12 @@ export const NextLoading = {
bodys.insertBefore(div, bodys.childNodes[0]);
},
// 移除 loading
done: () => {
done: (time: number = 1000) => {
nextTick(() => {
setTimeout(() => {
const el = document.querySelector('.loading-next');
el && el.parentNode?.removeChild(el);
}, 1000);
const el = <HTMLElement>document.querySelector('.loading-next');
el?.parentNode?.removeChild(el);
}, time);
});
},
};

View File

@@ -21,6 +21,14 @@ export function saveUser(userinfo: any) {
setLocal(UserKey, userinfo);
}
export function saveThemeConfig(themeConfig: any) {
setLocal('themeConfig', themeConfig);
}
export function getThemeConfig() {
return getLocal('themeConfig');
}
// 获取是否开启水印
export function getUseWatermark() {
return getLocal('useWatermark');

View File

@@ -43,9 +43,12 @@ function del() {
const watermark = {
use: () => {
setTimeout(() => {
const userinfo = getUser();
if (userinfo && getUseWatermark()) {
if (!userinfo) {
del();
}
setTimeout(() => {
if (getUseWatermark()) {
set(`${userinfo.username} ${dateFormat2('yyyy-MM-dd HH:mm:ss', new Date())}`);
} else {
del();

View File

@@ -29,7 +29,6 @@ const router = createRouter({
// 前端控制路由:初始化方法,防止刷新时丢失
export function initAllFun() {
NextLoading.start(); // 界面 loading 动画开始执行
const token = getToken(); // 获取浏览器缓存 token 值
if (!token) {
// 无 token 停止执行下一步
@@ -48,7 +47,6 @@ export function initAllFun() {
// 后端控制路由:执行路由数据初始化
export async function initBackEndControlRoutesFun() {
NextLoading.start(); // 界面 loading 动画开始执行
const token = getToken(); // 获取浏览器缓存 token 值
if (!token) {
// 无 token 停止执行下一步
@@ -232,6 +230,8 @@ export function resetRoute() {
}
export async function initRouter() {
NextLoading.start(); // 界面 loading 动画开始执行
try {
// 初始化方法执行
const { isRequestRoutes } = useThemeConfig(pinia).themeConfig;
if (!isRequestRoutes) {
@@ -241,6 +241,9 @@ export async function initRouter() {
// 后端控制路由isRequestRoutes 为 true则开启后端控制路由
await initBackEndControlRoutesFun();
}
} finally {
NextLoading.done();
}
}
let SysWs: any;
@@ -297,7 +300,6 @@ router.beforeEach(async (to, from, next) => {
// 路由加载后
router.afterEach(() => {
NProgress.done();
NextLoading.done();
});
// 导出路由

View File

@@ -140,5 +140,17 @@ export const useThemeConfig = defineStore('themeConfig', {
setThemeConfig(data: ThemeConfigState) {
this.themeConfig = data.themeConfig;
},
// 切换暗模式
switchDark(isDark: boolean) {
this.themeConfig.isDark = isDark;
const body = document.documentElement as HTMLElement;
if (isDark) {
body.setAttribute('class', 'dark');
this.themeConfig.editorTheme = 'vs-dark';
} else {
body.setAttribute('class', '');
this.themeConfig.editorTheme = 'SolarizedLight';
}
},
},
});

View File

@@ -83,3 +83,42 @@
opacity: 1;
}
}
/* 登录页动画
------------------------------- */
@keyframes loginLeft {
0% {
left: -100%;
}
50%,
100% {
left: 100%;
}
}
@keyframes loginTop {
0% {
top: -100%;
}
50%,
100% {
top: 100%;
}
}
@keyframes loginRight {
0% {
right: -100%;
}
50%,
100% {
right: 100%;
}
}
@keyframes loginBottom {
0% {
bottom: -100%;
}
50%,
100% {
bottom: 100%;
}
}

View File

@@ -340,3 +340,9 @@
background: linear-gradient(45deg, #b2e68d, #bce689);
right: 0;
}
.el-dialog {
border-radius: 6px; /* 设置圆角 */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 添加轻微阴影效果 */
}

View File

@@ -213,7 +213,7 @@ onUnmounted(() => {
}
.layout-lock-screen-img {
@extend .layout-lock-screen-fixed;
background: url('@/assets/image/bg-login.png') no-repeat;
background: url('@/assets/image/login-bg-main.svg') no-repeat;
background-size: 100% 100%;
z-index: 9999991;
}

View File

@@ -374,7 +374,7 @@
</div>
</div>
</div>
<div class="copy-config">
<!-- <div class="copy-config">
<el-alert title="点击下方按钮,复制布局配置去 /src/store/modules/themeConfig.ts中修改" type="warning" :closable="false"> </el-alert>
<el-button
size="small"
@@ -385,7 +385,7 @@
@click="onCopyConfigClick($event.target)"
>一键复制配置
</el-button>
</div>
</div> -->
</el-scrollbar>
</el-drawer>
</div>

View File

@@ -56,7 +56,7 @@
<crop />
</el-icon>
</div>
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
<el-dropdown trigger="click" :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
<span class="layout-navbars-breadcrumb-user-link" style="cursor: pointer">
<img :src="userInfo.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
{{ userInfo.name || userInfo.username }}
@@ -75,7 +75,7 @@
</template>
<script setup lang="ts" name="layoutBreadcrumbUser">
import { ref, computed, reactive, onMounted, nextTick } from 'vue';
import { ref, computed, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessageBox, ElMessage } from 'element-plus';
import screenfull from 'screenfull';
@@ -83,11 +83,12 @@ import { resetRoute } from '@/router/index';
import { storeToRefs } from 'pinia';
import { useUserInfo } from '@/store/userInfo';
import { useThemeConfig } from '@/store/themeConfig';
import { clearUser, clearSession, setLocal, getLocal, removeLocal } from '@/common/utils/storage';
import { clearSession, removeLocal } from '@/common/utils/storage';
import UserNews from '@/views/layout/navBars/breadcrumb/userNews.vue';
import SearchMenu from '@/views/layout/navBars/breadcrumb/search.vue';
import mittBus from '@/common/utils/mitt';
import openApi from '@/common/openApi';
import { saveThemeConfig, getThemeConfig } from '@/common/utils/storage';
const router = useRouter();
const searchRef = ref();
@@ -99,7 +100,8 @@ const state = reactive({
disabledSize: '',
});
const { userInfo } = storeToRefs(useUserInfo());
const { themeConfig } = storeToRefs(useThemeConfig());
const themeConfigStore = useThemeConfig();
const { themeConfig } = storeToRefs(themeConfigStore);
// 设置分割样式
const layoutUserFlexNum = computed(() => {
@@ -164,16 +166,8 @@ const onHandleCommandClick = (path: string) => {
};
const switchDark = (isDark: boolean) => {
themeConfig.value.isDark = isDark;
setLocal('themeConfig', themeConfig.value);
const body = document.documentElement as HTMLElement;
if (isDark) {
body.setAttribute('class', 'dark');
themeConfig.value.editorTheme = 'vs-dark';
} else {
body.setAttribute('class', '');
themeConfig.value.editorTheme = 'SolarizedLight';
}
themeConfigStore.switchDark(isDark);
saveThemeConfig(themeConfig.value);
};
// // 菜单搜索点击
@@ -185,7 +179,7 @@ const onSearchClick = () => {
const onComponentSizeChange = (size: string) => {
removeLocal('themeConfig');
themeConfig.value.globalComponentSize = size;
setLocal('themeConfig', themeConfig.value);
saveThemeConfig(themeConfig.value);
// proxy.$ELEMENT.size = size;
initComponentSize();
window.location.reload();
@@ -193,7 +187,7 @@ const onComponentSizeChange = (size: string) => {
// 初始化全局组件大小
const initComponentSize = () => {
switch (getLocal('themeConfig').globalComponentSize) {
switch (getThemeConfig().globalComponentSize) {
case '':
state.disabledSize = '';
break;
@@ -211,12 +205,10 @@ const initComponentSize = () => {
// 页面加载时
onMounted(() => {
if (getLocal('themeConfig')) {
const isDark = themeConfig.value.isDark;
state.isDark = isDark;
switchDark(isDark);
const themeConfig = getThemeConfig();
if (themeConfig) {
initComponentSize();
state.isDark = themeConfig.isDark;
}
});
</script>

View File

@@ -1,26 +1,35 @@
<template>
<div class="login-container">
<div class="login-logo">
<span>{{ themeConfig.globalViceTitle }}</span>
<div class="login-container flex">
<div class="login-left">
<div class="login-left-logo">
<img :src="logoMini" />
<div class="login-left-logo-text">
<span>mayfly-go</span>
<!-- <span class="login-left-logo-text-msg">mayfly-go</span> -->
</div>
<div class="login-content" :class="{ 'login-content-mobile': tabsActiveName === 'mobile' }">
<div class="login-content-main">
<h4 class="login-content-title">mayfly-go</h4>
<el-tabs v-model="tabsActiveName" @tab-click="onTabsClick">
<el-tab-pane label="账号密码登录" name="account" :disabled="tabsActiveName === 'account'">
<transition name="el-zoom-in-center">
<Account v-show="isTabPaneShow" ref="loginForm" />
</transition>
</div>
<div class="login-left-img">
<img :src="loginBgImg" />
</div>
<img :src="loginBgSplitImg" class="login-left-waves" />
</div>
<div class="login-right flex">
<div class="login-right-warp flex-margin">
<span class="login-right-warp-one"></span>
<span class="login-right-warp-two"></span>
<div class="login-right-warp-mian">
<div class="login-right-warp-main-title">mayfly-go</div>
<div class="login-right-warp-main-form">
<div v-if="!state.isScan">
<el-tabs v-model="state.tabsActiveName">
<el-tab-pane label="账号密码登录" name="account">
<Account ref="loginForm" />
</el-tab-pane>
<!-- <el-tab-pane label="手机号登录" name="mobile" :disabled="tabsActiveName === 'mobile'">
<transition name="el-zoom-in-center">
<Mobile v-show="!isTabPaneShow" />
</transition>
</el-tab-pane> -->
</el-tabs>
<div class="mt20" v-show="oauth2LoginConfig.enable">
</div>
<div class="mt20" v-show="state.oauth2LoginConfig.enable">
<el-button link size="small">第三方登录: </el-button>
<el-tooltip :content="oauth2LoginConfig.name" placement="top-start">
<el-tooltip :content="state.oauth2LoginConfig.name" placement="top-start">
<el-button link size="small" type="primary" @click="oauth2Login">
<el-icon :size="18">
<Link />
@@ -30,39 +39,43 @@
</div>
</div>
</div>
<!-- <div class="login-copyright">
<div class="mb5 login-copyright-company">mayfly</div>
<div class="login-copyright-msg">mayfly</div>
</div> -->
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { toRefs, reactive, onMounted, h, ref } from 'vue';
import Account from '@/views/login/component/AccountLogin.vue';
<script setup lang="ts" name="loginIndex">
import { ref, defineAsyncComponent, onMounted, reactive, computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
import logoMini from '@/assets/image/logo.svg';
import loginBgImg from '@/assets/image/login-bg-main.svg';
import loginBgSplitImg from '@/assets/image/login-bg-split.svg';
import openApi from '@/common/openApi';
import config from '@/common/config';
const { themeConfig } = storeToRefs(useThemeConfig());
// 引入组件
const Account = defineAsyncComponent(() => import('./component/AccountLogin.vue'));
const loginForm = ref<{ loginResDeal: (data: any) => void } | null>(null);
// 定义变量内容
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive({
tabsActiveName: 'account',
isTabPaneShow: true,
isScan: false,
oauth2LoginConfig: {
name: 'OAuth2登录',
enable: false,
},
});
const loginForm = ref<{ loginResDeal: (data: any) => void } | null>(null);
const { isTabPaneShow, tabsActiveName, oauth2LoginConfig: oauth2LoginConfig } = toRefs(state);
// 切换密码、手机登录
const onTabsClick = () => {
state.isTabPaneShow = !state.isTabPaneShow;
};
// 获取布局配置信息
const getThemeConfig = computed(() => {
return themeConfig.value;
});
onMounted(async () => {
state.oauth2LoginConfig = await openApi.oauth2LoginConfig();
@@ -94,76 +107,178 @@ const oauth2Login = () => {
<style scoped lang="scss">
.login-container {
width: 100%;
height: 100%;
background: url('@/assets/image/bg-login.png') no-repeat;
background-size: 100% 100%;
.login-logo {
position: absolute;
top: 30px;
left: 50%;
height: 50px;
background: var(--bg-main-color);
.login-left {
flex: 1;
position: relative;
background-color: rgba(211, 239, 255, 1);
margin-right: 100px;
.login-left-logo {
display: flex;
align-items: center;
font-size: 20px;
color: var(--el-color-primary);
letter-spacing: 2px;
width: 90%;
transform: translateX(-50%);
position: absolute;
top: 50px;
left: 80px;
z-index: 1;
animation: logoAnimation 0.3s ease;
img {
width: 52px;
height: 52px;
}
.login-content {
width: 500px;
padding: 20px;
.login-left-logo-text {
display: flex;
flex-direction: column;
span {
margin-left: 10px;
font-size: 28px;
color: #26a59a;
}
.login-left-logo-text-msg {
font-size: 12px;
color: #32a99e;
}
}
}
.login-left-img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) translate3d(0, 0, 0);
background-color: rgba(255, 255, 255, 0.99);
box-shadow: 0 2px 12px 0 var(--el-color-primary-light-5);
border-radius: 4px;
transition: height 0.2s linear;
height: 490px;
overflow: hidden;
z-index: 1;
.login-content-main {
margin: 0 auto;
width: 80%;
.login-content-title {
color: #333;
font-weight: 500;
font-size: 22px;
text-align: center;
letter-spacing: 4px;
margin: 15px 0 30px;
white-space: nowrap;
transform: translate(-50%, -50%);
width: 100%;
height: 52%;
img {
width: 100%;
height: 100%;
animation: error-num 0.6s ease;
}
}
}
.login-content-mobile {
height: 418px;
}
.login-copyright {
.login-left-waves {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 30px;
text-align: center;
color: white;
font-size: 12px;
opacity: 0.8;
.login-copyright-company {
white-space: nowrap;
top: 0;
right: -100px;
}
}
.login-right {
width: 700px;
.login-right-warp {
border: 1px solid var(--el-color-primary-light-3);
border-radius: 3px;
width: 500px;
height: 500px;
position: relative;
overflow: hidden;
background-color: var(--bg-main-color);
.login-right-warp-one,
.login-right-warp-two {
position: absolute;
display: block;
width: inherit;
height: inherit;
&::before,
&::after {
content: '';
position: absolute;
z-index: 1;
}
}
.login-right-warp-one {
&::before {
filter: hue-rotate(0deg);
top: 0px;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, transparent, var(--el-color-primary));
animation: loginLeft 3s linear infinite;
}
&::after {
filter: hue-rotate(60deg);
top: -100%;
right: 2px;
width: 3px;
height: 100%;
background: linear-gradient(180deg, transparent, var(--el-color-primary));
animation: loginTop 3s linear infinite;
animation-delay: 0.7s;
}
}
.login-right-warp-two {
&::before {
filter: hue-rotate(120deg);
bottom: 2px;
right: -100%;
width: 100%;
height: 3px;
background: linear-gradient(270deg, transparent, var(--el-color-primary));
animation: loginRight 3s linear infinite;
animation-delay: 1.4s;
}
&::after {
filter: hue-rotate(300deg);
bottom: -100%;
left: 0px;
width: 3px;
height: 100%;
background: linear-gradient(360deg, transparent, var(--el-color-primary));
animation: loginBottom 3s linear infinite;
animation-delay: 2.1s;
}
}
.login-right-warp-mian {
display: flex;
flex-direction: column;
height: 100%;
.login-right-warp-main-title {
height: 130px;
line-height: 130px;
font-size: 27px;
text-align: center;
letter-spacing: 3px;
animation: logoAnimation 0.3s ease;
animation-delay: 0.3s;
color: var(--el-text-color-primary);
}
.login-right-warp-main-form {
flex: 1;
padding: 0 50px 50px;
.login-content-main-sacn {
position: absolute;
top: 0;
right: 0;
width: 50px;
height: 50px;
overflow: hidden;
cursor: pointer;
transition: all ease 0.3s;
color: var(--el-color-primary);
&-delta {
position: absolute;
width: 35px;
height: 70px;
z-index: 2;
top: 2px;
right: 21px;
background: var(--el-color-white);
transform: rotate(-45deg);
}
&:hover {
opacity: 1;
transition: all ease 0.3s;
color: var(--el-color-primary) !important;
}
i {
width: 47px;
height: 50px;
display: inline-block;
font-size: 48px;
position: absolute;
right: 1px;
top: 0px;
}
}
}
}
.login-copyright-msg {
@extend .login-copyright-company;
}
}
}

View File

@@ -6,18 +6,19 @@ import (
"errors"
"fmt"
"io"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/stringx"
"mime/multipart"
"net/http"
"os"
"time"
)
var client = &http.Client{}
// 默认超时
const DefTimeout = 60
type RequestWrapper struct {
client http.Client
url string
method string
timeout int
@@ -34,7 +35,7 @@ type MultipartFile struct {
// 创建一个请求
func NewRequest(url string) *RequestWrapper {
return &RequestWrapper{url: url}
return &RequestWrapper{url: url, client: http.Client{}}
}
func (r *RequestWrapper) Url(url string) *RequestWrapper {
@@ -55,15 +56,13 @@ func (r *RequestWrapper) Timeout(timeout int) *RequestWrapper {
return r
}
func (r *RequestWrapper) GetByParam(paramMap map[string]string) *ResponseWrapper {
func (r *RequestWrapper) GetByQuery(queryMap map[string]any) *ResponseWrapper {
var params string
for k, v := range paramMap {
for k, v := range queryMap {
if params != "" {
params += "&"
} else {
params += "?"
}
params += k + "=" + v
params += k + "=" + stringx.AnyToStr(v)
}
r.url += "?" + params
return r.Get()
@@ -72,7 +71,7 @@ func (r *RequestWrapper) GetByParam(paramMap map[string]string) *ResponseWrapper
func (r *RequestWrapper) Get() *ResponseWrapper {
r.method = "GET"
r.body = nil
return request(r)
return sendRequest(r)
}
func (r *RequestWrapper) PostJson(body string) *ResponseWrapper {
@@ -83,18 +82,18 @@ func (r *RequestWrapper) PostJson(body string) *ResponseWrapper {
r.header = make(map[string]string)
}
r.header["Content-type"] = "application/json"
return request(r)
return sendRequest(r)
}
func (r *RequestWrapper) PostObj(body any) *ResponseWrapper {
marshal, err := json.Marshal(body)
if err != nil {
return createRequestError(errors.New("解析json obj错误"))
return &ResponseWrapper{err: errors.New("解析json obj错误")}
}
return r.PostJson(string(marshal))
}
func (r *RequestWrapper) PostParams(params string) *ResponseWrapper {
func (r *RequestWrapper) PostForm(params string) *ResponseWrapper {
buf := bytes.NewBufferString(params)
r.method = "POST"
r.body = buf
@@ -102,7 +101,7 @@ func (r *RequestWrapper) PostParams(params string) *ResponseWrapper {
r.header = make(map[string]string)
}
r.header["Content-type"] = "application/x-www-form-urlencoded"
return request(r)
return sendRequest(r)
}
func (r *RequestWrapper) PostMulipart(files []MultipartFile, reqParams map[string]string) *ResponseWrapper {
@@ -115,7 +114,7 @@ func (r *RequestWrapper) PostMulipart(files []MultipartFile, reqParams map[strin
if uploadFile.FilePath != "" {
file, err := os.Open(uploadFile.FilePath)
if err != nil {
return createRequestError(err)
return &ResponseWrapper{err: err}
}
defer file.Close()
reader = file
@@ -125,18 +124,18 @@ func (r *RequestWrapper) PostMulipart(files []MultipartFile, reqParams map[strin
part, err := writer.CreateFormFile(uploadFile.FieldName, uploadFile.FileName)
if err != nil {
return createRequestError(err)
return &ResponseWrapper{err: err}
}
_, err = io.Copy(part, reader)
io.Copy(part, reader)
}
// 如果有其他参数则写入body
for k, v := range reqParams {
if err := writer.WriteField(k, v); err != nil {
return createRequestError(err)
return &ResponseWrapper{err: err}
}
}
if err := writer.Close(); err != nil {
return createRequestError(err)
return &ResponseWrapper{err: err}
}
r.method = "POST"
@@ -145,74 +144,26 @@ func (r *RequestWrapper) PostMulipart(files []MultipartFile, reqParams map[strin
r.header = make(map[string]string)
}
r.header["Content-type"] = writer.FormDataContentType()
return request(r)
return sendRequest(r)
}
type ResponseWrapper struct {
StatusCode int
Body []byte
Header http.Header
}
func (r *ResponseWrapper) IsSuccess() bool {
return r.StatusCode == 200
}
func (r *ResponseWrapper) BodyToObj(objPtr any) error {
_ = json.Unmarshal(r.Body, &objPtr)
return r.getError()
}
func (r *ResponseWrapper) BodyToString() (string, error) {
return string(r.Body), r.getError()
}
func (r *ResponseWrapper) BodyToMap() (map[string]any, error) {
var res map[string]any
err := json.Unmarshal(r.Body, &res)
if err != nil {
return nil, err
}
return res, r.getError()
}
func (r *ResponseWrapper) getError() error {
if !r.IsSuccess() {
return errors.New(string(r.Body))
}
return nil
}
func request(rw *RequestWrapper) *ResponseWrapper {
wrapper := &ResponseWrapper{StatusCode: 0, Header: make(http.Header)}
func sendRequest(rw *RequestWrapper) *ResponseWrapper {
respWrapper := &ResponseWrapper{}
timeout := rw.timeout
if timeout > 0 {
client.Timeout = time.Duration(timeout) * time.Second
rw.client.Timeout = time.Duration(timeout) * time.Second
} else {
timeout = DefTimeout
}
req, err := http.NewRequest(rw.method, rw.url, rw.body)
if err != nil {
return createRequestError(err)
respWrapper.err = fmt.Errorf("创建请求错误-%s", err.Error())
return respWrapper
}
setRequestHeader(req, rw.header)
resp, err := client.Do(req)
if err != nil {
wrapper.Body = []byte(fmt.Sprintf("执行HTTP请求错误-%s", err.Error()))
return wrapper
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
wrapper.Body = []byte(fmt.Sprintf("读取HTTP请求返回值失败-%s", err.Error()))
return wrapper
}
wrapper.StatusCode = resp.StatusCode
wrapper.Body = body
wrapper.Header = resp.Header
return wrapper
resp, err := rw.client.Do(req)
return &ResponseWrapper{resp: resp, err: err}
}
func setRequestHeader(req *http.Request, header map[string]string) {
@@ -222,6 +173,73 @@ func setRequestHeader(req *http.Request, header map[string]string) {
}
}
func createRequestError(err error) *ResponseWrapper {
return &ResponseWrapper{0, []byte(fmt.Sprintf("创建HTTP请求错误-%s", err.Error())), make(http.Header)}
type ResponseWrapper struct {
resp *http.Response
err error
}
// 将响应体通过json解析转为指定结构体
func (r *ResponseWrapper) BodyToObj(objPtr any) error {
bodyBytes, err := r.BodyBytes()
if err != nil {
return err
}
err = json.Unmarshal(bodyBytes, &objPtr)
if err != nil {
return fmt.Errorf("解析响应体-json解析失败-%s", err.Error())
}
return nil
}
// 将响应体转为strings
func (r *ResponseWrapper) BodyToString() (string, error) {
bodyBytes, err := r.BodyBytes()
if err != nil {
return "", err
}
return string(bodyBytes), nil
}
// 将响应体通过json解析转为map
func (r *ResponseWrapper) BodyToMap() (map[string]any, error) {
var res map[string]any
return res, r.BodyToObj(&res)
}
// 获取响应体的字节数组
func (r *ResponseWrapper) BodyBytes() ([]byte, error) {
resp, err := r.GetHttpResp()
if err != nil {
return nil, err
}
body, err := io.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("读取响应体数据失败-%s", err.Error())
}
return body, err
}
// 获取http响应结果结构体
func (r *ResponseWrapper) GetHttpResp() (*http.Response, error) {
if r.err != nil {
return nil, fmt.Errorf("请求失败-%s", r.err.Error())
}
if r.resp == nil {
return nil, errors.New("请求失败-响应结构体为空,请检查请求url等信息")
}
statusCode := r.resp.StatusCode
if isFailureStatusCode(statusCode) {
logx.Warnf("请求响应状态码为为失败状态: %v", statusCode)
}
return r.resp, nil
}
func isFailureStatusCode(statusCode int) bool {
return statusCode < http.StatusOK || statusCode >= http.StatusBadRequest
}

View File

@@ -0,0 +1,34 @@
package httpclient
import (
"fmt"
"testing"
)
type TestStruct struct {
Id uint64
Username string
}
func TestGet(t *testing.T) {
res, err := NewRequest("www.baidu.com").Get().BodyToString()
fmt.Println(err)
fmt.Println(res)
}
func TestGetBodyToMap(t *testing.T) {
res, err := NewRequest("http://go.mayfly.run/api/syslogs?pageNum=1&pageSize=10").Get().BodyToMap()
fmt.Println(err)
fmt.Println(res["msg"])
fmt.Println(res["code"])
}
func TestGetQueryBodyToMap(t *testing.T) {
res, err := NewRequest("http://go.mayfly.run/api/syslogs").
Header("Authorization", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTUzOTQ5NTIsImlkIjoxLCJ1c2VybmFtZSI6ImFkbWluIn0.pGrczVZqk5nlId-FZPkjW_O5Sw3-2yjgzACp_j4JEXY").
GetByQuery(map[string]any{"pageNum": 1, "pageSize": 10}).
BodyToMap()
fmt.Println(err)
fmt.Println(res["msg"])
fmt.Println(res["code"])
}