SpringCloud微服務實戰——搭建企業級開發框架(四十七):【移動開發】整合uni-app搭建移動端快速開發框架-添加Axios並實現登錄功能

来源:https://www.cnblogs.com/FullStackProgrammer/archive/2022/10/27/16831832.html
-Advertisement-
Play Games

uni-app自帶uni.request用於網路請求,因為我們需要自定義攔截器等功能,也是為了和我們後臺管理保持統一,這裡我們使用比較流行且功能更強大的axios來實現網路請求。 Axios 是一個基於 promise 網路請求庫,作用於node.js 和瀏覽器中。 它是 isomorphic 的( ...


  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基座

image.png

3、彈出框會顯示我們已連接的模擬器,點擊運行,HBuilderX就可以自動打包app發佈到模擬器中運行,並可以在HBuilderX控制台查看運行日誌。

image.png

4、在手機模擬器展示的登錄界面中,輸入我們系統用戶的手機號碼 + 密碼,登錄成功後即可跳轉到登錄後的界面。

image.png
image.png

源碼地址:

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

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 記CentOS7的httpd源碼包安裝的實驗過程 註:以下純屬本人(小白)見解,如有錯誤請求各位大佬幫忙指正。新人寫博有啥錯誤也麻煩各位大佬給個指點。 作者:lonely-sail 相關下載: httpd依賴包:apr、apr-util(官網下載:http://apr.apache.org/)、pc ...
  • 架構圖: 多網段: 客戶機位於192.168.10.x/24網段 RS位於:10.0.0.x/24網段 VIP:位於172.16.0.x/24網關 通信過程: 客戶機(CIP) >lvs(VIP) >lvs(DIP) >RS(RIP) >客戶機(CIP) 說明: DR模式要求每個RS都擁有和lvs一 ...
  • 架構圖: 環境: 一臺:客戶端 eth0:僅主機 192.168.10.6/24 GW:192.168.10.200 一臺:ROUTER eth0 :NAT 10.0.0.200/24 eth1: 僅主機 192.168.10.200/24 啟用 IP_FORWARD 一臺:LVS eth0:NAT ...
  • 一、前言 作為全鏈路數字化技術與服務提供商,袋鼠雲提供了從數據湖、大數據基礎平臺、離線開發、實時開發、數據服務、數據治理、指標管理、客戶數據洞察、數據孿生可視化等全產品體系的服務。 圍繞著“行業應用”及“通用應用”,袋鼠雲聚焦數智提供全維數字解決方案,幫助企業實現降本增效、快捷轉型,迄今為止袋鼠雲已 ...
  • 摘要:華為雲資料庫創新Lab在論文《MARINA: An MLP-Attention Model for Multivariate Time-Series Analysis》中提出了華為自研的自回歸時序神經網路模型,可用於時序數據的預測以及異常檢測。 本文分享自華為雲社區《CIKM'22 MARIN ...
  • openeuler安裝教程 1. 下載 鏡像版本:openEuler-20.03-LTS-x86_64-dvd.iso 下載地址:https://repo.openeuler.org/openEuler-20.03-LTS/ISO/x86_64/ 需要工具:vmware 2.安裝操作 完成 ...
  • 摘要:隨著offset的增加,查詢的時長也會越來越長。當offset達到百萬級別的時候查詢時長通常是業務所不能容忍的。 本文分享自華為雲社區《offset新探索:雙管齊下,加速大數據量查詢》,作者: GaussDB 資料庫 。 眾所周知,在各類業務中時常會用到LIMIT y offset x來做跳過 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 我們知道,在打包Android App之前,我們需要先通過HX生成打包資源。如果是通過cli創建的項目,則通過以下命令生成打包資源: yarn build:app-plus 生成打包資源後的目錄長這樣: 然後將整個目錄中的所有文件拷貝到A ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...