react-native的token认证流程

在 React Native 中实现 Token 认证是移动应用开发中的常见需求,它用于验证用户的身份并授权其访问受保护的 API 资源。

Token 认证的核心流程:

  1. 用户登录 (Login):

    • 用户在前端输入用户名和密码。
    • 前端将这些凭据发送到后端 API。
    • 后端验证凭据。如果验证成功,后端会生成一个 Token(通常是 JWT - JSON Web Token)并返回给前端。
    • 前端接收到 Token 后,将其安全地存储起来(例如使用 AsyncStorage)。
    • 前端更新用户的登录状态(例如 Context 或 Redux 中的状态)。
  2. 访问受保护资源 (Access Protected Resources):

    • 前端需要访问后端受保护的 API 时,将之前保存的 Token 附带在请求的 Authorization 头中发送给后端。
    • 后端接收到请求后,验证 Token 的有效性(例如检查签名、有效期、是否被吊销等)。
    • 如果 Token 有效,后端处理请求并返回数据。
    • 如果 Token 无效(过期、篡改等),后端会返回错误(通常是 401 Unauthorized 或 403 Forbidden),前端需要处理这些错误(例如引导用户重新登录)。
  3. Token 刷新 (Token Refresh - 可选但推荐):

    • 为了安全性,Token 通常有较短的有效期。
    • 当访问 Token 过期时,前端可以使用一个 Refresh Token(通常有效期较长,也存储在客户端)向后端请求一个新的访问 Token。
    • 后端验证 Refresh Token,如果有效,则签发新的访问 Token。
    • 这减少了用户频繁重新登录的次数,同时保持了安全性。
  4. 用户登出 (Logout):

    • 用户选择登出时,前端从 AsyncStorage 中移除保存的 Token。
    • 前端清除用户相关的状态。
    • (可选)通知后端使该 Token 失效(如果后端支持)。

React Native 中的实现细节:

我们将使用以下技术栈:

  • @react-native-async-storage/async-storage: 用于在设备上持久化存储 Token。
  • Context API (或 Redux/Zustand 等状态管理库): 用于在整个应用中管理用户的登录状态和 Token。
  • fetch API (或 axios): 用于进行网络请求。
  • crypto-js (可选,用于密码哈希): 在发送密码到后端前进行哈希处理,增加一层客户端安全性(尽管主要安全在于 HTTPS 和后端处理)。

详细代码实现与示例

我们将构建一个简化的应用,包含:

  1. UserContext.tsx: 管理用户登录状态和 Token。
  2. AuthScreen.tsx: 登录界面。
  3. HomeScreen.tsx: 登录后的主界面,可以访问受保护资源并登出。
  4. App.tsx: 应用入口,根据登录状态决定显示哪个界面。
1. 安装必要的库

Bash

npm install @react-native-async-storage/async-storage crypto-js @react-navigation/native @react-navigation/stack
npx expo install react-native-screens react-native-safe-area-context # For react-navigation
# 或
yarn add @react-native-async-storage/async-storage crypto-js @react-navigation/native @react-navigation/stack
yarn expo install react-native-screens react-native-safe-area-context
2. 后端模拟 (Python Flask 示例)

为了能够运行前端代码,你需要一个简单的后端。这里提供一个 Python Flask 的极简示例。

backend/app.py

from flask import Flask, request, jsonify
from flask_cors import CORS
import jwt
import datetime
import hashlib # 用于密码哈希app = Flask(__name__)
CORS(app) # 允许跨域请求SECRET_KEY = "your_super_secret_key_for_jwt" # 生产环境请使用更复杂的密钥
REFRESH_SECRET_KEY = "your_super_secret_refresh_key_for_jwt"# 简单模拟的用户数据库
users_db = {"testuser": {"password_hash": hashlib.sha256("testpassword".encode('utf-8')).hexdigest(), # 密码哈希"user_id": "user123","user_name": "Test User","email": "test@example.com","phone": "123-456-7890"}
}@app.route('/api/user_login', methods=['POST'])
def user_login():data = request.get_json()username = data.get('username')password_hash = data.get('password') # 接收前端传来的哈希后的密码if not username or not password_hash:return jsonify({"message": "用户名和密码不能为空"}), 400user_data = users_db.get(username)if user_data and user_data["password_hash"] == password_hash:# 生成访问 Token (有效期短,例如 15 分钟)access_token_payload = {"user_id": user_data["user_id"],"user_name": user_data["user_name"],"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15)}access_token = jwt.encode(access_token_payload, SECRET_KEY, algorithm="HS256")# 生成刷新 Token (有效期长,例如 7 天)refresh_token_payload = {"user_id": user_data["user_id"],"exp": datetime.datetime.utcnow() + datetime.timedelta(days=7)}refresh_token = jwt.encode(refresh_token_payload, REFRESH_SECRET_KEY, algorithm="HS256")return jsonify({"token": access_token,"refresh_token": refresh_token, # 将 refresh_token 也返回"user_id": user_data["user_id"],"user_name": user_data["user_name"],"email": user_data["email"],"phone": user_data["phone"],"message": "登录成功"}), 200else:return jsonify({"message": "无效的用户名或密码"}), 401@app.route('/api/protected_data', methods=['GET'])
def protected_data():auth_header = request.headers.get('Authorization')if not auth_header or not auth_header.startswith('Bearer '):return jsonify({"message": "未授权:缺少或无效的Authorization头"}), 401token = auth_header.split(" ")[1]try:payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])user_id = payload.get('user_id')user_name = payload.get('user_name', '未知用户')return jsonify({"message": f"欢迎, {user_name}! 这是受保护的数据。", "user_id": user_id}), 200except jwt.ExpiredSignatureError:return jsonify({"message": "未授权:Token 已过期"}), 401except jwt.InvalidTokenError:return jsonify({"message": "未授权:无效的 Token"}), 401@app.route('/api/refresh_token', methods=['POST'])
def refresh_token():data = request.get_json()refresh_token = data.get('refresh_token')if not refresh_token:return jsonify({"message": "未授权:缺少刷新Token"}), 400try:payload = jwt.decode(refresh_token, REFRESH_SECRET_KEY, algorithms=["HS256"])user_id = payload.get('user_id')# 重新生成新的访问 Tokenaccess_token_payload = {"user_id": user_id,"user_name": users_db.get(user_id, {}).get("user_name", "未知用户"), # 重新获取用户名"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15)}new_access_token = jwt.encode(access_token_payload, SECRET_KEY, algorithm="HS256")# 也可以同时生成新的刷新 Token,以实现刷新 Token 的滚动更新new_refresh_token_payload = {"user_id": user_id,"exp": datetime.datetime.utcnow() + datetime.timedelta(days=7)}new_refresh_token = jwt.encode(new_refresh_token_payload, REFRESH_SECRET_KEY, algorithm="HS256")return jsonify({"token": new_access_token,"refresh_token": new_refresh_token # 返回新的刷新 Token}), 200except jwt.ExpiredSignatureError:return jsonify({"message": "未授权:刷新 Token 已过期,请重新登录"}), 401except jwt.InvalidTokenError:return jsonify({"message": "未授权:无效的刷新 Token"}), 401if __name__ == '__main__':# 注意:10.0.2.2 是 Android 模拟器访问宿主机(开发机器)的默认 IP# 如果是 iOS 模拟器或真实设备,请使用你电脑的局域网 IPapp.run(host='10.0.2.2', port=5000, debug=True)

运行后端:

  1. 保存为 backend/app.py
  2. 安装依赖:pip install Flask Flask-Cors PyJWT
  3. 运行:python backend/app.py
3. React Native 前端代码
src/contexts/UserContext.tsx

这是核心,负责管理用户认证状态、Token 的存储与获取。

// src/contexts/UserContext.tsx
import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { SHA256 } from 'crypto-js'; // 用于前端密码哈希// 定义用户信息接口
export interface UserInfo {user_id: string;user_name: string;email: string;phone: string;status: 'logged_in' | 'logged_out';
}// 定义 Context 的类型
interface UserContextType {user: UserInfo | null; // 当前登录的用户信息authToken: string | null; // 访问 TokenrefreshToken: string | null; // 刷新 Tokenlogin: (username: string, password: string) => Promise<void>; // 登录方法logout: () => void; // 登出方法isLoading: boolean; // 是否正在加载用户数据 (应用启动时)refreshAccessToken: () => Promise<boolean>; // 刷新访问 Token 的方法
}// 创建 Context,并设置默认值
const UserContext = createContext<UserContextType>({user: null,authToken: null,refreshToken: null,login: async () => { /* no-op */ },logout: () => { /* no-op */ },isLoading: true, // 初始加载状态为 truerefreshAccessToken: async () => false, // 默认返回 false
});// UserProvider 组件
const UserProvider = ({ children }: { children: ReactNode }) => {const [user, setUser] = useState<UserInfo | null>(null);const [authToken, setAuthToken] = useState<string | null>(null);const [refreshToken, setRefreshToken] = useState<string | null>(null);const [isLoading, setIsLoading] = useState(true);// --- 1. 应用启动时加载存储的用户和 Token 信息 ---useEffect(() => {const loadStoredAuth = async () => {try {const storedAuthToken = await AsyncStorage.getItem('authToken');const storedRefreshToken = await AsyncStorage.getItem('refreshToken');const storedUserId = await AsyncStorage.getItem('userId');const storedUserName = await AsyncStorage.getItem('userName'); // 假设也存储了用户名if (storedAuthToken && storedRefreshToken && storedUserId && storedUserName) {setAuthToken(storedAuthToken);setRefreshToken(storedRefreshToken);// 这里可以根据实际情况从 API 重新获取完整的用户信息,// 或者直接使用存储的信息(如果它足够完整)setUser({user_id: storedUserId,user_name: storedUserName,email: 'N/A', // 示例,实际应从API获取或存储phone: 'N/A', // 示例status: 'logged_in',});console.log('User and tokens loaded from AsyncStorage.');} else {console.log('No existing user data or tokens found.');setUser(null);setAuthToken(null);setRefreshToken(null);}} catch (error) {console.error('Error loading user data from AsyncStorage:', error);setUser(null);setAuthToken(null);setRefreshToken(null);} finally {setIsLoading(false); // 加载完成}};loadStoredAuth();}, []); // 空依赖数组,只在组件挂载时运行一次// --- 2. 登录方法 ---const login = useCallback(async (username: string, password: string) => {try {const hashedPassword = SHA256(password).toString(); // 客户端密码哈希console.log('Hashed Password (Frontend):', hashedPassword);const response = await fetch('http://10.0.2.2:5000/api/user_login', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({ username, password: hashedPassword }),});if (!response.ok) {const errorData = await response.json();throw new Error(errorData.message || 'Login failed');}const data = await response.json();if (!data.token || !data.refresh_token || !data.user_id) {throw new Error('Invalid API response structure: missing token or user_id');}// 保存 Token 和用户 IDawait AsyncStorage.setItem('authToken', data.token);await AsyncStorage.setItem('refreshToken', data.refresh_token);await AsyncStorage.setItem('userId', data.user_id);await AsyncStorage.setItem('userName', data.user_name); // 也存储用户名// 更新 Context 状态setAuthToken(data.token);setRefreshToken(data.refresh_token);setUser({user_id: data.user_id,user_name: data.user_name || username,email: data.email || 'N/A',phone: data.phone || 'N/A',status: 'logged_in',});console.log('Login successful, user and tokens set.');} catch (error) {console.error('Login error:', error);// 登录失败时清除所有状态await logout(); // 调用登出方法清除所有相关信息throw error; // 重新抛出错误以便调用方处理}}, []); // 空依赖数组,确保 login 函数的稳定性// --- 3. 登出方法 ---const logout = useCallback(async () => {console.log('Logging out...');await AsyncStorage.removeItem('authToken');await AsyncStorage.removeItem('refreshToken');await AsyncStorage.removeItem('userId');await AsyncStorage.removeItem('userName');setUser(null);setAuthToken(null);setRefreshToken(null);console.log('User logged out, tokens cleared.');}, []); // 空依赖数组,确保 logout 函数的稳定性// --- 4. 刷新访问 Token 的方法 ---const refreshAccessToken = useCallback(async (): Promise<boolean> => {if (!refreshToken) {console.warn('No refresh token available. Cannot refresh access token.');await logout(); // 没有刷新 Token,视为需要重新登录return false;}try {console.log('Attempting to refresh access token...');const response = await fetch('http://10.0.2.2:5000/api/refresh_token', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ refresh_token: refreshToken }),});if (!response.ok) {const errorData = await response.json();console.error('Failed to refresh token:', errorData.message);await logout(); // 刷新失败,强制登出return false;}const data = await response.json();if (!data.token || !data.refresh_token) { // 后端可能也返回新的刷新tokenconsole.error('Invalid refresh token response.');await logout();return false;}await AsyncStorage.setItem('authToken', data.token);await AsyncStorage.setItem('refreshToken', data.refresh_token); // 保存新的刷新 TokensetAuthToken(data.token);setRefreshToken(data.refresh_token); // 更新状态console.log('Access token refreshed successfully.');return true;} catch (error) {console.error('Error during token refresh:', error);await logout(); // 网络或其他错误,强制登出return false;}}, [refreshToken, logout]); // 依赖 refreshToken 和 logout,确保是最新的// 提供的 Context 值const contextValue = {user,authToken,refreshToken,login,logout,isLoading,refreshAccessToken,};return (<UserContext.Provider value={contextValue}>{children}</UserContext.Provider>);
};export default UserProvider;// 自定义 Hook,方便组件使用 Context
export const useUser = () => useContext(UserContext);
src/screens/AuthScreen.tsx

登录界面,用户输入凭据并调用 login 方法。

// src/screens/AuthScreen.tsx
import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet, ActivityIndicator, Alert } from 'react-native';
import { useUser } from '../contexts/UserContext'; // 引入 useUser Hookfunction AuthScreen() {const [username, setUsername] = useState('testuser'); // 默认用户名const [password, setPassword] = useState('testpassword'); // 默认密码const [isAuthenticating, setIsAuthenticating] = useState(false); // 登录状态const { login } = useUser(); // 获取登录方法const handleLogin = async () => {setIsAuthenticating(true); // 开始登录try {await login(username, password); // 调用 Context 中的登录方法// 登录成功,Context 会自动更新 user 状态,App.tsx 会负责导航} catch (error: any) {Alert.alert('登录失败', error.message || '请检查用户名和密码。');console.error('Login error in AuthScreen:', error);} finally {setIsAuthenticating(false); // 结束登录}};return (<View style={styles.container}><Text style={styles.title}>用户登录</Text><TextInputstyle={styles.input}placeholder="用户名"value={username}onChangeText={setUsername}autoCapitalize="none"/><TextInputstyle={styles.input}placeholder="密码"value={password}onChangeText={setPassword}secureTextEntry/><Buttontitle={isAuthenticating ? "登录中..." : "登录"}onPress={handleLogin}disabled={isAuthenticating} // 登录中禁用按钮/>{isAuthenticating && <ActivityIndicator style={styles.spinner} size="small" color="#0000ff" />}</View>);
}const styles = StyleSheet.create({container: {flex: 1,justifyContent: 'center',alignItems: 'center',padding: 20,backgroundColor: '#f5f5f5',},title: {fontSize: 28,fontWeight: 'bold',marginBottom: 30,color: '#333',},input: {width: '100%',padding: 15,borderWidth: 1,borderColor: '#ddd',borderRadius: 8,marginBottom: 15,backgroundColor: '#fff',},spinner: {marginTop: 10,},
});export default AuthScreen;
src/screens/HomeScreen.tsx

登录后的主界面,可以访问受保护资源并登出。

TypeScript

// src/screens/HomeScreen.tsx
import React, { useState, useEffect } from 'react';
import { View, Text, Button, StyleSheet, ActivityIndicator, Alert } from 'react-native';
import { useUser } from '../contexts/UserContext'; // 引入 useUser Hookfunction HomeScreen() {const { user, authToken, logout, refreshAccessToken } = useUser(); // 获取用户信息、token、登出、刷新方法const [protectedData, setProtectedData] = useState<string | null>(null);const [isFetchingData, setIsFetchingData] = useState(false);// --- 访问受保护数据的方法 ---const fetchProtectedData = async () => {if (!authToken) {Alert.alert('错误', '没有访问 Token,请重新登录。');return;}setIsFetchingData(true);try {const response = await fetch('http://10.0.2.2:5000/api/protected_data', {headers: {'Authorization': `Bearer ${authToken}`, // 在 Authorization 头中携带 Token},});if (response.status === 401) { // Unauthorized (Token 可能过期或无效)const errorData = await response.json();if (errorData.message === "未授权:Token 已过期") {console.log('Access Token expired, attempting to refresh...');const refreshed = await refreshAccessToken(); // 尝试刷新 Tokenif (refreshed) {// 如果刷新成功,可以考虑重新尝试本次请求 (更高级的实现会在拦截器中自动重试)Alert.alert('提示', '访问 Token 已刷新,请重新尝试获取数据。');} else {Alert.alert('会话过期', '您的会话已过期,请重新登录。');}} else {Alert.alert('认证失败', errorData.message || '无效的访问 Token。');}await logout(); // 遇到 401 且无法刷新,通常需要登出return;}if (!response.ok) {const errorData = await response.json();throw new Error(errorData.message || '获取受保护数据失败。');}const data = await response.json();setProtectedData(data.message);} catch (error: any) {Alert.alert('错误', `获取数据失败: ${error.message}`);console.error('Error fetching protected data:', error);} finally {setIsFetchingData(false);}};return (<View style={styles.container}><Text style={styles.title}>欢迎,{user?.user_name || '用户'}!</Text><Text style={styles.info}>您的用户ID: {user?.user_id}</Text>{authToken && (<Text style={styles.info}>访问 Token: {authToken.substring(0, 20)}...</Text>)}{refreshToken && (<Text style={styles.info}>刷新 Token: {refreshToken.substring(0, 20)}...</Text>)}<Buttontitle={isFetchingData ? "获取中..." : "获取受保护数据"}onPress={fetchProtectedData}disabled={isFetchingData || !authToken} // 没有 Token 或正在获取时禁用/>{isFetchingData && <ActivityIndicator style={styles.spinner} size="small" color="#0000ff" />}{protectedData && (<View style={styles.dataContainer}><Text style={styles.dataTitle}>受保护数据:</Text><Text style={styles.dataText}>{protectedData}</Text></View>)}<View style={styles.logoutButtonContainer}><Button title="登出" onPress={logout} color="red" /></View></View>);
}const styles = StyleSheet.create({container: {flex: 1,justifyContent: 'center',alignItems: 'center',padding: 20,backgroundColor: '#f5f5f5',},title: {fontSize: 24,fontWeight: 'bold',marginBottom: 10,color: '#333',},info: {fontSize: 16,marginBottom: 5,color: '#555',},spinner: {marginTop: 10,},dataContainer: {marginTop: 30,padding: 15,borderWidth: 1,borderColor: '#ccc',borderRadius: 8,backgroundColor: '#e9e9e9',width: '100%',alignItems: 'center',},dataTitle: {fontSize: 18,fontWeight: 'bold',marginBottom: 10,color: '#333',},dataText: {fontSize: 16,textAlign: 'center',color: '#666',},logoutButtonContainer: {marginTop: 40,width: '80%',},
});export default HomeScreen;
src/navigation/MainNavigator.tsx (主应用内部导航)

TypeScript

// src/navigation/MainNavigator.tsx
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from '../screens/HomeScreen';
// 你可以添加更多登录后的页面const Stack = createStackNavigator();function MainNavigator() {return (<Stack.Navigator screenOptions={{ headerShown: false }}><Stack.Screen name="Home" component={HomeScreen} />{/* <Stack.Screen name="Settings" component={SettingsScreen} /> */}</Stack.Navigator>);
}export default MainNavigator;
src/navigation/AuthNavigator.tsx (认证页面导航)
// src/navigation/AuthNavigator.tsx
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import AuthScreen from '../screens/AuthScreen';const Stack = createStackNavigator();function AuthNavigator() {return (<Stack.Navigator screenOptions={{ headerShown: false }}><Stack.Screen name="Login" component={AuthScreen} />{/* <Stack.Screen name="Register" component={RegisterScreen} /> */}</Stack.Navigator>);
}export default AuthNavigator;
App.tsx (应用入口)

这是应用的主文件,它设置了 UserProvider 并根据用户的登录状态来切换导航器。

TypeScript

// App.tsx
import 'react-native-gesture-handler'; // react-navigation 必备
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { ActivityIndicator, View, StyleSheet, Text } from 'react-native';import UserProvider, { useUser } from './src/contexts/UserContext'; // 引入你的 UserProvider 和 useUser Hook
import MainNavigator from './src/navigation/MainNavigator';
import AuthNavigator from './src/navigation/AuthNavigator';const Stack = createStackNavigator();// 根导航器,根据认证状态切换主/认证流程
function RootNavigator() {const { user, isLoading } = useUser(); // 从 Context 获取 user 和 isLoading 状态if (isLoading) {// 如果正在加载用户数据,显示加载指示器或启动屏return (<View style={styles.loadingContainer}><ActivityIndicator size="large" color="#0000ff" /><Text style={styles.loadingText}>正在加载应用数据...</Text></View>);}return (<Stack.Navigator screenOptions={{ headerShown: false }}>{user ? (// 如果 user 不为 null (已登录),显示主应用界面<Stack.Screen name="Main" component={MainNavigator} />) : (// 如果 user 为 null (未登录),显示认证界面<Stack.Screen name="Auth" component={AuthNavigator} />)}</Stack.Navigator>);
}export default function App() {return (<NavigationContainer><UserProvider><RootNavigator /></UserProvider></NavigationContainer>);
}const styles = StyleSheet.create({loadingContainer: {flex: 1,justifyContent: 'center',alignItems: 'center',backgroundColor: '#fff',},loadingText: {marginTop: 10,fontSize: 16,color: '#555',},
});
4. 项目结构
my-react-native-app/
├── App.tsx
├── package.json
├── src/
│   ├── contexts/
│   │   └── UserContext.tsx
│   ├── navigation/
│   │   ├── AuthNavigator.tsx
│   │   └── MainNavigator.tsx
│   └── screens/
│       ├── AuthScreen.tsx
│       └── HomeScreen.tsx
└── backend/ (你的后端代码,例如 app.py)└── app.py

总结

  • UserProvider: 是 Token 认证的核心。它负责在应用启动时从 AsyncStorage 中加载 Token,管理登录/登出逻辑,并通过 Context API 将 user 信息、authTokenrefreshTokenisLoading 状态暴露给整个应用。
  • isLoading 状态: 用于在应用启动时,等待 UserProviderAsyncStorage 加载完数据。在这期间,你应该显示一个加载指示器,避免闪烁或提前渲染错误内容。
  • login 方法: 用户登录成功后,从后端获取 Token 并将其保存在 AsyncStorage 和 Context 状态中。
  • logout 方法: 清除 AsyncStorage 中的 Token 和 Context 状态。
  • refreshAccessToken 方法: 处理访问 Token 过期的情况。它会尝试使用刷新 Token 获取新的访问 Token。
  • 网络请求: 在需要访问受保护资源时,从 useUser() 中获取 authToken,并将其添加到请求的 Authorization: Bearer <token> 头中。
  • 错误处理: 特别是对于 401/403 错误,需要捕获并引导用户重新认证(例如,跳转到登录页)。

这个示例提供了一个基本但完整的 React Native Token 认证流程。在生产环境中,你可能还需要考虑更复杂的错误处理、Token 有效性检查、安全性增强(如 HTTPS)、更强大的网络请求库(如 Axios 及其拦截器)以及更完善的用户信息管理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/82634.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Dify:详解 docker-compose.yaml配置文件

详解 docker-compose.yaml 配置文件 docker-compose.yaml 是用于定义和运行多容器 Docker 应用的配置文件。下面&#xff0c;我们将详细解释您提供的 docker-compose.yaml 文件&#xff0c;包括各个服务的作用、配置&#xff0c;以及它们与 .env 文件之间的关系。 文件概览 自…

Python基于Django的主观题自动阅卷系统【附源码、文档说明】

博主介绍&#xff1a;✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…

今日行情明日机会——20250528

上证指数缩量收小阴线&#xff0c;个股跌多涨少&#xff0c;总体情绪偏差&#xff0c;注意风险为主。 深证指数&#xff0c;缩量收小阴线&#xff0c;连续5天阴线&#xff0c;明后天反弹的概率增大&#xff0c;但仍要注意风险。 2025年5月28日涨停股主要行业方向分析 1. 无人…

基于stm32LORA无线抄表系统仿真

资料下载地址&#xff1a;基于stm32LORA无线抄表系统仿真 1、项目介绍 基于LoRa的无线通信的电力抄表系统&#xff0c;采集节点数据&#xff0c;通过LoRa无线通信进行数据传输&#xff0c;最后再网关节点上显示。 2、仿真图 3、仿真代码 #include "oled.h" #incl…

不同电脑同一个网络ip地址一样吗

不同电脑在连接同一个WiFi时&#xff0c;它们的IP地址会相同吗&#xff1f;相信不少朋友都对这个问题感到好奇&#xff0c;今天我们就来详细探讨一下。 一、基础概念&#xff1a;IP地址的本质与分类 IP地址是分配给网络设备的唯一标识符&#xff0c;用于在互联网或局域网中定位…

CentOS 7 下 Redis 从 5.0 升级至 7.4.3 全流程实践

目录 前言1 查看 Redis 运行情况与配置1.1 查看 Redis 是否正在运行1.2 连接 Redis 服务并获取配置信息1.3 查找 redis.conf 配置文件位置 2 关闭旧版本 Redis 实例2.1 使用客户端命令关闭 Redis2.2 验证 Redis 是否完全关闭 3 升级 GCC 编译环境3.1 检查当前 GCC 版本3.2 安装…

SQLord: 基于反向数据生成和任务拆解的 Text-to-SQL 企业落地方案

曾在Text-to-SQL方向做过深入的研究&#xff0c;以此为基础研发的DataAgent在B2B平台成功落地&#xff0c;因此作为第一作者&#xff0c;在 The Web Conference (WWW’2025, CCF-A) 会议上发表了相关论文&#xff1a; SQLord: A Robust Enterprise Text-to-SQL Solution via R…

内网搭建NTS服务器

内网搭建NTS服务器 关键字 : ntp nts ipv6 NTS 是 Network Time Security&#xff08;网络时间安全&#xff09;的缩写,是 NTP 的一种安全扩展机制。它利用传输层安全&#xff08;TLS&#xff09;和相关数据的认证加密&#xff08;AEAD&#xff09;&#xff0c;为 NTP 的客户…

AD9268、AD9643调试过程中遇到的问题

Ad9268芯片 AD9268是一款双通道、16位、80 MSPS/105 MSPS/125 MSPS模数转换器(ADC)。AD9268旨在支持要求高性能、低成本、小尺寸和多功能的通信应用。双通道ADC内核采用多级差分流水线架构&#xff0c;集成输出纠错逻辑。每个ADC都具有宽带宽、差分采样保持模拟输入放大器&…

用豆包写单元测试

用豆包写单元测试&#xff0c; 输入 vue 模板内容&#xff0c;输入 参考vue模板内容写一个单元测试要求用jest.mock实现构造完成&#xff0c;修复bug。npm run test:unit – tests/unit/views/xxx/xxx.spec.js看下 % Stmts 语句覆盖率&#xff1a;执行到的代码语句占总语句的比…

css样式块重复调用

通译灵码解释。还给了一些示例&#xff0c;包含传参等内容 scss和sass的区别。scss与sass是两种样式编写风格&#xff0c;scss是大括号加;号形式。而sass是缩进的格式使用scss为什么要要安装sass呢。sass是一门css预处理器语言。所以要安装。

【深度学习新浪潮】以图搜地点是如何实现的?(含大模型方案)

1. 以图搜地点的实现方式有哪些? 扫描手机照片中的截图并识别出位置信息,主要有以下几种实现方式: 通过照片元数据获取: 原理:现代智能手机拍摄的照片通常会包含Exif(Exchangeable Image File)元数据。Exif中除了有像素信息之外,还包含了光圈、快门、白平衡、ISO、焦距…

DeepSeek R1 与 V3 的全面对比,两个版本有什么差别?

DeepSeek R1与DeepSeek V3是深度求索&#xff08;DeepSeek&#xff09;公司推出的两款定位不同的大语言模型&#xff0c;界面上用户可选择基础模型(V3)、深度思考(R1)、联网搜索。 基础模型(V3)是DeepSeek的标配,没有勾选默认就是基础模型。为了让用户更清晰地了解两款模型的差…

Spring Boot 深度集成 Ollama 指南:从聊天模型配置到生产级应用开发

Spring Boot 深度集成 Ollama 指南&#xff1a;从聊天模型配置到生产级应用开发 前言 在人工智能应用开发中&#xff0c;大语言模型&#xff08;LLM&#xff09;的本地化部署需求日益增长。Ollama 作为开源的本地LLM运行平台&#xff0c;支持Mistral、LLaMA等主流模型&#x…

查询oracle进程数和会话数进行优化

查看当前参数配置 首先需要查询当前的 processes 和 sessions 参数值&#xff0c;以确定是否需要调整。 SQL SHOW PARAMETER processes; SHOW PARAMETER sessions; 这些命令可以显示当前实例中允许的最大进程数和会话数 查询当前连接数&#xff0c;查询并发会话 SELECT COUNT…

顶会新方向:卡尔曼滤波+目标检测

卡尔曼虑波&#xff0b;目标检测创新结合&#xff0c;新作准确率突破100%! 一个有前景且好发论文的方向:卡尔曼滤波&#xff0b;目标检测! 这种创新结合&#xff0c;得到学术界的广泛认可&#xff0c;多篇成果陆续登上顶会顶刊。例如无人机竞速系统 Swift&#xff0c;登上nat…

运维自动化工具 ansible 知识点总结

1.Ansible 基础 1.1 Ansible简介 Ansible 是一个开源软件&#xff0c;提供配置管理和应用程序部署等项目通用的管理功能。它主要运行在类 Unix 系统上&#xff0c;通过特性语言来描述各种资源对象&#xff0c;进而管理类 Unix 系统和 Microsoft Windows 系统等系统资源。 官网…

基于python,html,flask,echart,ids/ips,VMware,mysql,在线sdn防御ddos系统

详细视频:【基于python,html,flask,echart,ids/ips,VMware,mysql,在线sdn防御ddos系统-哔哩哔哩】 https://b23.tv/azUqQXe

C语言进阶--数据的存储

1.数据类型介绍 内置类型 char //字符数据类型 1字节 short //短整型 2字节 int //整型 4字节 long //长整型 4/8字节 long long //更长的整型 8字节 (C99中引入的) float //单精度浮点数 4字节 double //双精度浮点数 8字节sizeof(long…

C++学习细节回顾(汇总三)

一.多态概念 同样是动物叫的⼀个⾏为(函数)&#xff0c;传猫对象过去&#xff0c;就是”(>ω<)喵“&#xff0c;传狗对象过去&#xff0c;就是"汪汪"。 1.根据对象不同类型&#xff0c;调用不同函数&#xff0c;这就叫做运行时多态(动态多态) 2.编译时多态(静态…