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

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,118元/月
云原生网关 MSE Higress,422元/月
简介: 在uni-app中,使用axios实现网络请求和登录功能涉及以下几个关键步骤:1. **安装axios和axios-auth-refresh**: 在项目的`package.json`中添加axios和axios-auth-refresh依赖,可以通过HBuilderX的终端窗口运行`yarn add axios axios-auth-refresh`命令来安装。2. **配置自定义常量**: 创建`project.config.js`文件,配置全局常量,如API基础URL、TenantId、APP_CLIENT_ID和APP_CLIENT_SECRET等。

  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打开终端窗口
  • 菜单栏中,选择 视图 > 显示终端©
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、在手机模拟器展示的登录界面中,输入我们系统用户的手机号码 + 密码,登录成功后即可跳转到登录后的界面。

GitEgg-Cloud是一款基于SpringCloud整合搭建的企业级微服务应用开发框架,开源项目地址:

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


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

相关文章
|
1天前
|
XML 移动开发 前端开发
使用duxapp开发 React Native App 事半功倍
对于Taro的壳子,或者原生React Native,都会存在 `android` `ios`这两个文件夹,而在duxapp中,这些文件夹的内容是自动生成的,那么对于需要在这些文件夹中修改的配置内容,例如包名、版本号、新架构开关等,都通过配置文件的方式配置了,而不需要需修改具体的文件
|
1天前
|
存储 开发工具 Android开发
使用.NET MAUI开发第一个安卓APP
【9月更文挑战第24天】使用.NET MAUI开发首个安卓APP需完成以下步骤:首先,安装Visual Studio 2022并勾选“.NET Multi-platform App UI development”工作负载;接着,安装Android SDK。然后,创建新项目时选择“.NET Multi-platform App (MAUI)”模板,并仅针对Android平台进行配置。了解项目结构,包括`.csproj`配置文件、`Properties`配置文件夹、平台特定代码及共享代码等。
|
6天前
|
JSON 资源调度 JavaScript
Vue框架中Ajax请求的实现方式:使用axios库或fetch API
选择 `axios`还是 `fetch`取决于项目需求和个人偏好。`axios`提供了更丰富的API和更灵活的错误处理方式,适用于需要复杂请求配置的场景。而 `fetch`作为现代浏览器的原生API,使用起来更为简洁,但在旧浏览器兼容性和某些高级特性上可能略显不足。无论选择哪种方式,它们都能有效地在Vue应用中实现Ajax请求的功能。
15 4
|
26天前
|
Web App开发 Java 视频直播
FFmpeg开发笔记(四十九)助您在毕业设计中脱颖而出的几个流行APP
对于软件、计算机等专业的毕业生,毕业设计需实现实用软件或APP。新颖的设计应结合最新技术,如5G时代的音视频技术。示例包括: 1. **短视频分享APP**: 集成FFmpeg实现视频剪辑功能,如添加字幕、转场特效等。 2. **电商购物APP**: 具备直播带货功能,使用RTMP/SRT协议支持流畅直播体验。 3. **同城生活APP**: 引入WebRTC技术实现可信的视频通话功能。这些应用不仅实用,还能展示开发者紧跟技术潮流的能力。
63 4
FFmpeg开发笔记(四十九)助您在毕业设计中脱颖而出的几个流行APP
|
21天前
|
移动开发 小程序 JavaScript
uni-app开发微信小程序
本文详细介绍如何使用 uni-app 开发微信小程序,涵盖需求分析、架构思路及实施方案。主要功能包括用户登录、商品列表展示、商品详情、购物车及订单管理。技术栈采用 uni-app、uView UI 和 RESTful API。文章通过具体示例代码展示了从初始化项目、配置全局样式到实现各页面组件及 API 接口的全过程,并提供了完整的文件结构和配置文件示例。此外,还介绍了微信授权登录及后端接口模拟方法,确保项目的稳定性和安全性。通过本教程,读者可快速掌握使用 uni-app 开发微信小程序的方法。
52 3
|
1月前
|
Web App开发 Android开发
FFmpeg开发笔记(四十六)利用SRT协议构建手机APP的直播Demo
实时数据传输在互联网中至关重要,不仅支持即时通讯如QQ、微信的文字与图片传输,还包括音视频通信。一对一通信常采用WebRTC技术,如《Android Studio开发实战》中的App集成示例;而一对多的在线直播则需部署独立的流媒体服务器,使用如SRT等协议。SRT因其优越的直播质量正逐渐成为主流。本文档概述了SRT协议的使用,包括通过OBS Studio和SRT Streamer进行SRT直播推流的方法,并展示了推流与拉流的成功实例。更多细节参见《FFmpeg开发实战》一书。
41 1
FFmpeg开发笔记(四十六)利用SRT协议构建手机APP的直播Demo
|
1月前
|
Web App开发 5G Linux
FFmpeg开发笔记(四十四)毕业设计可做的几个拉满颜值的音视频APP
一年一度的毕业季来临,计算机专业的毕业设计尤为重要,不仅关乎学业评价还积累实战经验。选择紧跟5G技术趋势的音视频APP作为课题极具吸引力。这里推荐三类应用:一是融合WebRTC技术实现视频通话的即时通信APP;二是具备在线直播功能的短视频分享平台,涉及RTMP/SRT等直播技术;三是具有自定义动画特效及卡拉OK歌词字幕功能的视频剪辑工具。这些项目不仅技术含量高,也符合市场需求,是毕业设计的理想选择。
63 6
FFmpeg开发笔记(四十四)毕业设计可做的几个拉满颜值的音视频APP
|
1月前
|
编解码 Java Android开发
FFmpeg开发笔记(四十五)使用SRT Streamer开启APP直播推流
​SRT Streamer是一个安卓手机端的开源SRT协议直播推流框架,可用于RTMP直播和SRT直播。SRT Streamer支持的视频编码包括H264、H265等等,支持的音频编码包括AAC、OPUS等等,可谓功能强大的APP直播框架。另一款APP直播框架RTMP Streamer支持RTMP直播和RTSP直播,不支持SRT协议的直播。而本文讲述的SRT Streamer支持RTMP直播和SRT直播,不支持RTSP协议的直播。有关RTMP Streamer的说明参见之前的文章《使用RTMP Streamer开启APP直播推流》,下面介绍如何使用SRT Streamer开启手机直播。
54 4
FFmpeg开发笔记(四十五)使用SRT Streamer开启APP直播推流
|
1月前
|
IDE Java 开发工具
探索安卓开发之旅:打造你的第一款App
【8月更文挑战第24天】在这篇文章中,我们将一起踏上激动人心的安卓开发之旅。不论你是编程新手还是希望扩展技能的老手,本文将为你提供一份详尽指南,帮助你理解安卓开发的基础知识并实现你的第一个应用程序。从搭建开发环境到编写“Hello World”,每一步都将用浅显易懂的语言进行解释。那么,让我们开始吧!
|
2月前
|
存储 开发框架 安全
鸿蒙 HarmonyOS NEXT星河版APP应用开发-阶段一
HarmonyOS NEXT星河版的应用开发标志着华为分布式操作系统的全新篇章,它聚焦于打造原生精致、易用、流畅、安全、智能和互联的极致体验。开发者可以利用其先进的API和工具集,如DevEco Studio,构建高性能、跨设备无缝协同的应用程序,从而充分利用HarmonyOS的分布式能力,为用户带来一致且丰富的多场景数字生活体验。随着“学习强国”、岚图汽车、中国电信等知名企业和应用的加入,鸿蒙生态正迅速扩展,引领着原生应用开发的新趋势。
88 3
鸿蒙 HarmonyOS NEXT星河版APP应用开发-阶段一