uni-app自带uni.request用于网络请求,因为我们需要自定义拦截器等功能,也是为了和我们后台管理保持统一,这里我们使用比较流行且功能更强大的axios来实现网络请求。

  Axios 是一个基于 promise 网络请求库,作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。

Axios特性:
  • 从浏览器创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防御XSRF

一、安装axios和axios-auth-refresh组件

1、新增uni-app自定义常量配置文件

  HBuilderX有针对于uni-app的通用配置,很多通用常量可以直接配置在manifest.json文件中,且HBuilderX提供图形化配置界面。但我们有许多业务系统相关的常量配置,那么就需要一个自定义常量配置文件。

  uni-app中定义全局常量有多种方式,在vue.js框架中也可以使用App.vue里面的globalData,根据业务需求,自定义常量可能会很多,不便于和官方配置融合在一起,所以这里使用新增配置project.config.js文件并挂载Vue.prototype的方式来实现常量配置。

  • 在工程的根目录下新增project.config.js
 module.exports = {
# 配置请求后台地址
APP_API_BASE_URL: 'http://127.0.0.1:8080',
# 多租户项目,这里是默认的租户id
APP_TENANT_ID: '0',
# OAuth2授权的用户名密码
APP_CLIENT_ID: 'gitegg-admin',
# client_id:client_secret加密后的值,直接传,不需要再进行BASE64加密
APP_CLIENT_SECRET: 'Z2l0ZWdnLWFkbWluOjEyMzQ1Ng=='
}
  • 载main.js中导入、挂载project.config.js
 // 导入js文件
import ProjectConfig from './project.config'
// 挂载
Vue.prototype.$ProjectConfig = ProjectConfig
  • 在项目中引用
this.$ProjectConfig.APP_API_BASE_URL
  • 如果是在APP挂在前引用,那么使用以下方法引用
import ProjectConfig from './project.config'
2、打开HBuilderX终端命令窗口,用于执行yarn安装命令

HBuilderX默认没有开启终端命令窗口,选中项目,有两种方式打开命令窗口:

  • 按快捷键Ctrl+Alt+T打开终端窗口
  • 菜单栏中,选择 视图 > 显示终端(C)
3、执行安装axios(http请求拦截)和 axios-auth-refresh(强大的token刷新)组件命令
yarn add axios
yarn add axios-auth-refresh
4、在目录/common/utils新建axios.js,创建Axios 实例
const VueAxios = {
vm: {},
// eslint-disable-next-line no-unused-vars
install (Vue, instance) {
if (this.installed) {
return
}
this.installed = true
if (!instance) {
// eslint-disable-next-line no-console
console.error('You have to install axios')
return
}
Vue.axios = instance
Object.defineProperties(Vue.prototype, {
axios: {
get: function get () {
return instance
}
},
$http: {
get: function get () {
return instance
}
}
})
}
}
export {
VueAxios
}
5、在目录/common/utils新建request.js,自定义Axios拦截器

在这里定义拦截器主要用于:自动设置token、token过期刷新、统一异常提示、返回数据处理等功能。

import axios from 'axios'
import createAuthRefreshInterceptor from 'axios-auth-refresh'
import store from '@/store'
import { serialize } from '@/common/util'
import { VueAxios } from './axios'
import { ACCESS_TOKEN, REFRESH_ACCESS_TOKEN } from '@/store/mutation-types'
// 导入js文件
import ProjectConfig from '@/project.config.js' // uni-app适配
axios.defaults.adapter = function(config) {
return new Promise((resolve, reject) => {
var settle = require('axios/lib/core/settle');
var buildURL = require('axios/lib/helpers/buildURL');
uni.request({
method: config.method.toUpperCase(),
url: config.baseURL + buildURL(config.url, config.params, config.paramsSerializer),
header: config.headers,
data: config.data,
dataType: config.dataType,
responseType: config.responseType,
sslVerify: config.sslVerify,
complete: function complete(response) {
response = {
data: response.data,
status: response.statusCode,
errMsg: response.errMsg,
header: response.header,
config: config
};
settle(resolve, reject, response);
}
})
})
} // 创建 axios 实例
const request = axios.create({
// API 请求的默认前缀
baseURL: ProjectConfig.APP_API_BASE_URL,
timeout: 30000 // 请求超时时间
}) // 当token失效时,需要调用的刷新token的方法
const refreshAuthLogic = failedRequest =>
axios.post(ProjectConfig.APP_API_BASE_URL + '/oauth/token',
serialize({
grant_type: 'refresh_token',
refresh_token: uni.getStorageSync(REFRESH_ACCESS_TOKEN)
}),
{
headers: { 'TenantId': ProjectConfig.APP_TENANT_ID, 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Basic ' + ProjectConfig.APP_CLIENT_SECRET },
skipAuthRefresh: true // 刷新token请求过期,不再进行刷新
}
).then(tokenRefreshResponse => {
if (tokenRefreshResponse.status === 200 && tokenRefreshResponse.data && tokenRefreshResponse.data.success) {
const result = tokenRefreshResponse.data.data
uni.setStorageSync(ACCESS_TOKEN, result.tokenHead + result.token, result.expiresIn * 1000)
uni.setStorageSync(REFRESH_ACCESS_TOKEN, result.refreshToken, result.refreshExpiresIn * 1000)
failedRequest.response.config.headers['Authorization'] = result.tokenHead + result.token
} else if (tokenRefreshResponse.status === 200 && tokenRefreshResponse.data &&
!tokenRefreshResponse.data.success && tokenRefreshResponse.data.code === 401) {
store.dispatch('Timeout').then(async () => {
uni.navigateTo({
url: '/pages/login/login'
})
})
}
return Promise.resolve()
}) // 初始化刷新token拦截器
createAuthRefreshInterceptor(request, refreshAuthLogic, {
pauseInstanceWhileRefreshing: true // 当刷新token执行时,暂停其他请求
}) // 异常拦截处理器
const errorHandler = (error) => {
if (error.response) {
const data = error.response.data
if (error.response.status === 403) {
uni.showToast({
title: '您没有权限访问此接口',
icon:'error',
duration: 2000
});
} else if (error.response.status === 401 && !(data.result && data.result.isLogin)) {
// 当刷新token超时,则调到登录页面
uni.showModal({
title: '登录超时',
content: '由于您长时间未操作, 为确保安全, 请重新登录系统进行后续操作 !',
confirmText: '重新登录',
showCancel: false,
success: (res) => {
if(res.confirm) {
store.dispatch('Timeout').then(() => {
uni.navigateTo({
url: '/pages/login/login'
})
})
}
}
})
}
}
return Promise.reject(error)
} // request interceptor
request.interceptors.request.use(config => {
const token = uni.getStorageSync(ACCESS_TOKEN)
// 如果 token 存在
// 让每个请求携带自定义 token 请根据实际情况自行修改
if (token && config.authenticationScheme !== 'Basic') {
config.headers['Authorization'] = token
}
config.headers['TenantId'] = ProjectConfig.APP_TENANT_ID
return config
}, errorHandler) // response interceptor
request.interceptors.response.use((response) => {
const res = response.data
if (res && res.code) {
if (res.code !== 200) {
uni.showToast({
title: '操作失败: ' + res.msg,
icon:'error',
duration: 2000
});
return Promise.reject(res || 'Error')
} else {
return response.data
}
} else {
return response
}
}, errorHandler) const installer = {
vm: {},
install (Vue) {
Vue.use(VueAxios, request)
}
} export default request export {
installer as VueAxios,
request as axios
}

二、请求后台接口并实现登录功能

1、新建api目录,用于存放所有后台请求的接口,在api目录下新建login目录,存放用于登录的相关接口
2、在/api/login目录下新增login.js
import request from '@/common/utils/request'
import ProjectConfig from '@/project.config.js' const loginApi = {
// 登录
Login: '/oauth/token',
// 退出登录
Logout: '/oauth/logout',
// 获取系统配置的验证码类型
CaptchaType: '/oauth/captcha/type',
// 获取图片验证码
ImageCaptcha: '/oauth/captcha/image',
// 发送短信验证码
SendSms: '/oauth/sms/captcha/send',
// 获取用户信息
UserInfo: '/system/account/user/info',
// 第三方登录
SocialLoginUrl: '/oauth/social/login/',
// 第三方登录回调
SocialLoginCallback: '/oauth/social/',
// 第三方用户绑定---通过手机号验证码绑定
SocialBindMobile: '/oauth/social/bind/mobile',
// 第三方用户绑定---通过账号密码绑定
SocialBindAccount: '/oauth/social/bind/account',
// 发送短信验证码
SmsSend: '/extension/sms/code/send',
// 校验短信验证码
SmsCheckPre: '/extension/sms/check/code/pre',
// 校验短信验证码
SmsCheck: '/extension/sms/check/code',
// 发送注册短信
SmsRegisterSend: '/system/account/register/sms/send',
// 账户注册
Register: '/system/account/register',
// 校验用户是否存在
CheckUserExist: '/system/account/register/check'
} export default loginApi /**
* OAuth2登录
* @param parameter
* @returns {*}
*/
export function login (parameter) {
return request({
url: loginApi.Login,
authenticationScheme: 'Basic',
method: 'post',
headers: { 'Authorization': 'Basic ' + ProjectConfig.APP_CLIENT_SECRET },
skipAuthRefresh: true,
data: parameter
})
} /**
* OAuth2退出登录
* @param parameter
* @returns {*}
*/
export function logout (parameter) {
return request({
url: loginApi.Logout,
method: 'post',
skipAuthRefresh: true,
data: parameter
})
} /**
* 获取验证码类型
* @param parameter
* @returns {*}
*/
export function getCaptchaType () {
return request({
url: loginApi.CaptchaType,
method: 'get'
})
} /**
* 获取图片验证码
* @param parameter
* @returns {*}
*/
export function getImageCaptcha () {
return request({
url: loginApi.ImageCaptcha,
method: 'get'
})
} /**
* 获取短信验证码
* @param parameter
* @returns {*}
*/
export function getSmsCaptcha (parameter) {
return request({
url: loginApi.SendSms,
method: 'post',
data: parameter
})
} /**
* 获取用户信息
* @param parameter
* @returns {*}
*/
export function getInfo () {
return request({
url: loginApi.UserInfo,
method: 'get'
})
} /**
* 获取第三方登录的URL
* @param {Object} socialType
*/
export function getSocialLoginUrl (socialType) {
return request({
url: loginApi.SocialLoginUrl + socialType,
method: 'get'
})
} /**
* 第三方登录回调地址
* @param {Object} socialType
* @param {Object} parameter
*/
export function socialLoginCallback (socialType, parameter) {
return request({
url: loginApi.SocialLoginCallback + socialType + '/callback',
method: 'get',
params: parameter
})
} /**
* 发送短信验证码
* @param {Object} parameter
*/
export function sendSmsCode (parameter) {
return request({
url: loginApi.SmsSend,
method: 'post',
data: parameter
})
} /**
* 校验短信验证码
* @param {Object} parameter
*/
export function checkSmsCode (parameter) {
return request({
url: loginApi.SmsCheckPre,
method: 'get',
params: parameter
})
} /**
* 发送注册短信验证码
* @param {Object} parameter
*/
export function smsRegisterSend (parameter) {
return request({
url: loginApi.SmsRegisterSend,
method: 'post',
data: parameter
})
} /**
* 校验用户是否存在
* @param {Object} parameter
*/
export function checkUserExist (parameter) {
return request({
url: loginApi.CheckUserExist,
method: 'post',
data: parameter
})
} /**
* 用户注册
* @param {Object} parameter
*/
export function userRegister (parameter) {
return request({
url: loginApi.Register,
method: 'post',
data: parameter
})
} /**
* 第三方用户绑定---通过手机号验证码绑定
* @param {Object} parameter
*/
export function userBindMobile (parameter) {
return request({
url: loginApi.SocialBindMobile,
method: 'post',
data: parameter
})
} /**
* 第三方用户绑定---通过账号密码绑定
* @param {Object} parameter
*/
export function userBindAccount (parameter) {
return request({
url: loginApi.SocialBindAccount,
method: 'post',
data: parameter
})
}

3、在/pages目录下创建login目录,新增login.vue登录页面,用于登录。

<!-- 蓝色简洁登录页面 -->
<template>
<view class="login-bg">
<br /><br /><br /><br /><br /><br /><br />
<view class="t-login">
<form class="cl">
<view class="t-a">
<image src="@/static/login/user.png"></image>
<input type="text" name="username" placeholder="请输入手机号码" maxlength="11" v-model="username" />
</view>
<view class="t-a">
<image src="@/static/login/pwd.png"></image>
<input type="password" name="password" maxlength="100" placeholder="请输入密码" v-model="password" />
</view>
<button @tap="login()">登 录</button>
<view class="t-c">
<text class="t-c-txt" @tap="reg()">注册账号</text>
<text @tap="forgotPwd()">忘记密码</text>
</view>
</form>
<view class="t-f"><text>—————— 其他登录方式 ——————</text></view>
<view class="t-e cl">
<view class="t-g" @tap="wxLogin()"><image src="@/static/login/wx2.png"></image></view>
<view class="t-g" @tap="zfbLogin()"><image src="@/static/login/qq2.png"></image></view>
<view class="t-g" @tap="zfbLogin()"><image src="@/static/login/wb.png"></image></view>
</view>
</view>
<image class="img-a" src="@/static/login/bg1.png"></image>
</view>
</template>
<script>
import md5 from '@/common/md5.min.js';
import { mapActions } from 'vuex'
import { ACCESS_TOKEN, REFRESH_ACCESS_TOKEN } from '@/store/mutation-types'
export default {
data() {
return {
username: '',
password: '',
grant_type: 'password'
};
},
onLoad() {},
methods: {
...mapActions(['Login', 'Logout']),
login() {
var that = this;
if (!that.username) {
uni.showToast({ title: '请输入手机号', icon: 'none' });
return;
}
if (!/^[1][3,4,5,7,8,9][0-9]{9}$/.test(that.username)) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' });
return;
}
if (!that.password) {
uni.showToast({ title: '请输入密码', icon: 'none' });
return;
}
const loginParams = {}
loginParams.username = that.username
loginParams.grant_type = 'password'
loginParams.password = md5(that.password)
that.Login(loginParams)
.then((res) => this.loginSuccess(res))
.catch(err => this.requestFailed(err))
.finally(() => { })
},
loginSuccess (res) {
// 判断是否记住密码
uni.showToast({ title: '登录成功!', icon: 'none' });
uni.switchTab({
url: '/pages/tabBar/component/component',
fail(err) {
console.log(err)
}
})
},
requestFailed (res) {
// 判断是否记住密码
uni.showToast({ title: '登录失败:' + res.msg, icon: 'none' });
},
//忘记密码
forgotPwd() {
uni.showToast({ title: '忘记密码', icon: 'none' });
},
//立刻注册
reg() {
uni.showToast({ title: '注册账号', icon: 'none' });
}
}
};
</script>
<style>
.img-a {
width: 100%;
position: absolute;
bottom: 0;
}
.login-bg {
height: 100vh;
background-image: url(/static/login/bg3.png);
}
.t-login {
width: 580rpx;
padding: 55rpx;
margin: 0 auto;
font-size: 28rpx;
background-color: #ffffff;
border-radius: 20rpx;
box-shadow: 0 5px 7px 0 rgba(0, 0, 0, 0.15);
z-index: 9;
}
.t-login button {
font-size: 28rpx;
background: linear-gradient(to right, #ff8f77, #fe519f);
color: #fff;
height: 90rpx;
line-height: 90rpx;
border-radius: 50rpx;
} .t-login input {
padding: 0 20rpx 0 120rpx;
height: 90rpx;
line-height: 90rpx;
margin-bottom: 50rpx;
background: #f6f6f6;
border: 1px solid #f6f6f6;
font-size: 28rpx;
border-radius: 50rpx;
}
.t-login .t-a {
position: relative;
}
.t-login .t-a image {
width: 40rpx;
height: 40rpx;
position: absolute;
left: 40rpx;
top: 28rpx;
padding-right: 20rpx;
}
.t-login .t-b {
text-align: left;
font-size: 46rpx;
color: #000;
padding: 300rpx 0 120rpx 0;
font-weight: bold;
}
.t-login .t-d {
text-align: center;
color: #999;
margin: 80rpx 0;
}
.t-login .t-c {
text-align: right;
color: #666666;
margin: 30rpx 30rpx 40rpx 0;
}
.t-login .t-c .t-c-txt {
margin-right: 300rpx;
}
.t-login .t-e {
text-align: center;
width: 600rpx;
margin: 40rpx auto 0;
}
.t-login .t-g {
float: left;
width: 33.33%;
} .t-login .t-e image {
width: 70rpx;
height: 70rpx;
}
.t-login .t-f {
text-align: center;
margin: 80rpx 0 0 0;
color: #999;
}
.t-login .t-f text {
margin-left: 20rpx;
color: #b9b9b9;
font-size: 27rpx;
}
.t-login .uni-input-placeholder {
color: #aeaeae;
}
.cl {
zoom: 1;
}
.cl:after {
clear: both;
display: block;
visibility: hidden;
height: 0;
content: '\20';
}
</style>
4、将页面中用到的图片,复制到/static/login目录下
5、配置pages.json文件,将新增的login.vue文件目录加入到配置中。pages.json类似于vue.js工程下的路由页面配置
6、在App.vue文件的onLaunch方法中新增判断,当token为空时,跳转到我们刚刚新建的登录界面。
			const token = uni.getStorageSync(ACCESS_TOKEN)
if(!token || token === ''){
uni.navigateTo({
url: '/pages/login/login'
})
} else {
console.log('已登录');
}

三、在手机模拟器中运行并预览登录界面

  上文中介绍了如果配置HBuilderX连接手机模拟器,预览并调试uni-app项目,这里我们通过以上配置和编写,实现了登录界面,现在我们可以在手机模拟器中查看刚刚写的登录页面了。

1、启动手机模拟器 > 双击桌面的nox_adb快捷方式
2、在HBuilder X中依次点击 运行 -> 运行到手机或模拟器 -> 运行到Android App基座

3、弹出框会显示我们已连接的模拟器,点击运行,HBuilderX就可以自动打包app发布到模拟器中运行,并可以在HBuilderX控制台查看运行日志。

4、在手机模拟器展示的登录界面中,输入我们系统用户的手机号码 + 密码,登录成功后即可跳转到登录后的界面。



源码地址:

Gitee: https://gitee.com/wmz1930/GitEgg

GitHub: https://github.com/wmz1930/GitEgg

SpringCloud微服务实战——搭建企业级开发框架(四十七):【移动开发】整合uni-app搭建移动端快速开发框架-添加Axios并实现登录功能的更多相关文章

  1. springcloud微服务实战--笔记

    目前对Springcloud对了解仅限于:“用[注册服务.配置服务]来统一管理其他微服务” 这个水平.有待提高 Springcloud微服务实战这本书是翟永超2017年5月写的,时间已经过去了两年,略 ...

  2. SpringCloud学习(SPRINGCLOUD微服务实战)一

    SpringCloud学习(SPRINGCLOUD微服务实战) springboot入门 1.配置文件 1.1可以自定义参数并在程序中使用 注解@component @value 例如 若配置文件为a ...

  3. SpringCloud微服务实战——搭建企业级开发框架(四十):使用Spring Security OAuth2实现单点登录(SSO)系统

    一.单点登录SSO介绍   目前每家企业或者平台都存在不止一套系统,由于历史原因每套系统采购于不同厂商,所以系统间都是相互独立的,都有自己的用户鉴权认证体系,当用户进行登录系统时,不得不记住每套系统的 ...

  4. SpringCloud微服务实战——搭建企业级开发框架(四十六):【移动开发】整合uni-app搭建移动端快速开发框架-环境搭建

      近年来uni-app发展势头迅猛,只要会vue.js,就可以开发一套代码,发布移动应用到iOS.Android.Web(响应式).以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/ ...

  5. SpringCloud微服务实战——搭建企业级开发框架(四十一):扩展JustAuth+SpringSecurity+Vue实现多租户系统微信扫码、钉钉扫码等第三方登录

      前面我们详细介绍了SSO.OAuth2的定义和实现原理,也举例说明了如何在微服务框架中使用spring-security-oauth2实现单点登录授权服务器和单点登录客户端.目前很多平台都提供了单 ...

  6. 【SpringCloud微服务实战】搭建企业级应用开发框架(一):架构说明

    SpringCloud分布式应用微服务系统架构图: SpringCloud分布式应用微服务系统组件列表: 微服务框架组件:Spring Boot2 + SpringCloud Hoxton.SR8 + ...

  7. SpringCloud微服务实战——搭建企业级开发框架(三十六):使用Spring Cloud Stream实现可灵活配置消息中间件的功能

      在以往消息队列的使用中,我们通常使用集成消息中间件开源包来实现对应功能,而消息中间件的实现又有多种,比如目前比较主流的ActiveMQ.RocketMQ.RabbitMQ.Kafka,Stream ...

  8. SpringCloud微服务实战——搭建企业级开发框架(二十三):Gateway+OAuth2+JWT实现微服务统一认证授权

      OAuth2是一个关于授权的开放标准,核心思路是通过各类认证手段(具体什么手段OAuth2不关心)认证用户身份,并颁发token(令牌),使得第三方应用可以使用该token(令牌)在限定时间.限定 ...

  9. 【SpringCloud微服务实战学习系列】服务治理Spring Cloud Eureka

    Spring Cloud Eureka是Spring Cloud Netflix微服务中的一部分,它基于NetFlix Sureka做了二次封装,主要负责完成微服务架构中的服务治理功能. 一.服务治理 ...

  10. SpringCloud微服务实战——第三章服务治理

    Spring Cloud Eureka 服务治理 是微服务架构中最核心最基本的模块.用于实现各个微服务实例的自动化注册与发现. 服务注册: 在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中 ...

随机推荐

  1. spring boot 中英文官方文档

    中文文档:http://oopsguy.com/documents/springboot-docs/1.5.4/index.html#boot-features 英文官方文档:https://docs ...

  2. openjdk的bug

    容器内就获取个cpu利用率,怎么就占用单核100%了呢 背景:这个是在centos7 + lxcfs 和jdk11 的环境上复现的 下面列一下我们是怎么排查并解这个问题的. 一.故障现象 oppo内核 ...

  3. ettercap之dns欺骗攻击

    前言:攻击机(kali)和受害机(win7)需在同一网段下 1.首先创建一个钓鱼克隆网站,这里我就利用CS来弄了 2.对Ettercap的dns文件进行编辑 3.开启ettercap 4.去受害机看看 ...

  4. 从零开始Blazor Server(14)--修改密码

    目前,我们只做了在用户管理里强行修改密码,而没有做用户自行修改密码的功能,今天我们来实现它. 首先,我们的用户密码修改最好的位置应该就是在头像下面的下拉菜单里,所以我们在那里的LinkTemplate ...

  5. 分布式版本控制系统Git的使用;

    1.什么是Git Git是一个分布式的版本控制软件: 软件,类似于qq.office等安装到电脑才能使用的工具. 版本控制,类似于毕业论文,视频剪辑等需要反复修改和保留原历史数据: 分布式 文件夹拷贝 ...

  6. [HNOI2010]弹飞绵羊 (平衡树,LCT动态树)

    题面 题解 因为每个点都只能向后跳到一个唯一的点,但可能不止一个点能跳到后面的某个相同的点, 所以我们把它抽象成一个森林.(思考:为什么是森林而不是树?) 子节点可以跳到父节点,根节点再跳就跳飞了. ...

  7. Java 解析Tiff深入研究

    最近在读取客户发过来的tiff文件是,底层竟然报错了,错误:bandOffsets.length is wrong!   没办法,因为错误消息出现在tiff的read中,因此就对 底层序中tiff读取 ...

  8. HiveSql调优系列之Hive严格模式,如何合理使用Hive严格模式

    目录 综述 1.严格模式 1.1 参数设置 1.2 查看参数 1.3 严格模式限制内容及对应参数设置 2.实际操作 2.1 分区表查询时必须指定分区 2.2 order by必须指定limit 2.3 ...

  9. 如何充分利用KingbaseES日志

    作为现代关系数据库中,KingbaseES带有许多用于微调的参数.需要考虑的领域之一是KingbaseES应该如何记录其活动.日志记录在Kingbases数据库管理中经常被忽略,如果不被忽略,通常会被 ...

  10. 使用 Loki 进行日志报警(一)

    转载自:https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247492352&idx=1&sn=9c0cc7927b ...