尚医通 (十七)手机登录

news2024/9/23 15:24:57

目录

  • 一、登录需求分析
  • 二、搭建service-user模块
  • 三、登录接口实现
    • 1、添加service接口与实现
    • 2、添加Mapper接口
    • 3、添加Controller方法
  • 四、手机验证码登录(生成token)
    • 1、使用JWT进行跨域身份验证
      • 1.1 传统用户身份验证
      • 1.2 解决方案
    • 2、JWT介绍
    • 3、整合JWT
    • 4、单点登录总结
  • 五、整合阿里云服务短信(云市场)
    • 1、新建短信微服务
    • 2、编写发送短信接口
    • 3、完善登录接口
  • 六、手机验证码登录前端
    • 1、用户登录前端

一、登录需求分析

在这里插入图片描述

1,登录采取弹出层的形式
2,登录方式:
(1)手机号码+手机验证码
(2)微信扫描
3,无注册界面,第一次登录根据手机号判断系统是否存在,如果不存在则自动注册
4,微信扫描登录成功必须绑定手机号码,即:第一次扫描成功后绑定手机号,以后登录扫描直接登录成功
5,网关统一判断登录状态,如何需要登录,页面弹出登录层

二、搭建service-user模块

1、在service下创建service_user模块
2、添加application.properties

# 服务端口
server.port=8203
# 服务名
spring.application.name=service-user

# 环境设置:dev、test、prod
spring.profiles.active=dev

# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.121.140:3306/yygh_user?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/donglin/yygh/user/mapper/xml/*.xml

3、创建数据库和表
在这里插入图片描述
使用mybatis_plus代码生成器生成对应的controller…
在这里插入图片描述

4、创建启动类

@SpringBootApplication
@ComponentScan(basePackages = "com.donglin")
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.donglin")
@MapperScan("com.donglin.yygh.user.mapper")
public class ServiceUserApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceUserApplication.class, args);
    }
}

5、配置GateWay网关
properties

#设置路由id
spring.cloud.gateway.routes[3].id=service-user
#设置路由的uri
spring.cloud.gateway.routes[3].uri=lb://service-user
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[3].predicates= Path=/*/userinfo/**

yml
在这里插入图片描述

三、登录接口实现

1、添加service接口与实现

1.1 创建UserInfoService定义方法

public interface UserInfoService extends IService<UserInfo> {

    Map<String, Object> login(LoginVo loginVo);

}

1.2 创建UserInfoServiceImpl实现方法

@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

    @Override
    public Map<String, Object> login(LoginVo loginVo) {
        //1.首先获取用户输入的手机号和验证码信息
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();
        //2.对接收的手机号和验证码做一个非空判断
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)){
            throw  new YyghException(20001,"数据为空");
        }
        //TODO 对验证码做进一步确认

        //4.是否手机首次登陆,如果首次登陆,就先网表中注册一下当前用户信息
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        wrapper.eq("phone",phone);
        UserInfo userInfo = baseMapper.selectOne(wrapper);
        if (userInfo == null){
            userInfo = new UserInfo();
            userInfo.setPhone(phone);
            userInfo.setCreateTime(new Date());
            userInfo.setStatus(1);
            baseMapper.insert(userInfo);
        }
        //5.验证用户的status
        if (userInfo.getStatus() == 0){
            throw new YyghException(20001,"用户已经禁用");
        }
        //6.返回信息
        HashMap<String, Object> map = new HashMap<>();
        String name = userInfo.getName();
        if (StringUtils.isEmpty(name)){
            name = userInfo.getNickName();
        }
        if (StringUtils.isEmpty(name)){
            name = userInfo.getPhone();
        }
        map.put("name",name);
        map.put("token","");
        return map;
    }
}

2、添加Mapper接口

public interface UserInfoMapper extends BaseMapper<UserInfo> {

}

3、添加Controller方法

创建UserInfoApiController添加方法

@RestController
@RequestMapping("/api/userinfo")
public class UserInfoApiController {

    @Autowired
    private UserInfoService userInfoService;

    @ApiOperation(value = "会员登录")
    @PostMapping("login")
    public R login(@RequestBody LoginVo loginVo) {
        Map<String, Object> info = userInfoService.login(loginVo);
        return R.ok().data(info);
    }
}

四、手机验证码登录(生成token)

1、使用JWT进行跨域身份验证

1.1 传统用户身份验证

在这里插入图片描述

  • Internet服务无法与用户身份验证分开。一般过程如下:
    1.用户向服务器发送用户名和密码。
    2.验证服务器后,相关数据(如用户角色,登录时间等)将保存在当前会话中。
    3.服务器向用户返回session_id,session信息都会写入到用户的Cookie。
    4.用户的每个后续请求都将通过在Cookie中取出session_id传给服务器。
    5.服务器收到session_id并对比之前保存的数据,确认用户的身份。

这种模式最大的问题是,没有分布式架构,无法支持横向扩展。

1.2 解决方案

session广播
将透明令牌存入cookie,将用户身份信息存入redis
另外一种灵活的解决方案:
使用自包含令牌,通过客户端保存数据,而服务器不保存会话数据。 JWT是这种解决方案的代表

2、JWT介绍

JWT工具
JWT(Json Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上
JWT最重要的作用就是对 token信息的防伪作用。
JWT的原理
一个JWT由三个部分组成:公共部分、私有部分、签名部分。最后由这三者组合进行base64编码得到JWT。
在这里插入图片描述
1、 公共部分

主要是该JWT的相关配置参数,比如签名的加密算法、格式类型、过期时间等等。
Key=ATGUIGU
2、 私有部分
用户自定义的内容,根据实际需要真正要封装的信息。
userInfo{用户的Id,用户的昵称nickName}
3、 签名部分
SaltiP: 当前服务器的Ip地址!{linux 中配置代理服务器的ip}
主要用户对JWT生成字符串的时候,进行加密{盐值}
最终组成 key+salt+userInfo -> token!
base64编码,并不是加密,只是把明文信息变成了不可见的字符串。但是其实只要用一些工具就可以把base64编码解成明文,所以不要在JWT中放入涉及私密的信息。

3、整合JWT

在service_utils模块添加依赖

<dependencies>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>

添加JWT工具类

public class JwtHelper {
    private static long tokenExpiration = 24*60*60*1000;
    private static String tokenSignKey = "123456";

    public static String createToken(Long userId, String userName) {
        String token = Jwts.builder()
                .setSubject("YYGH-USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .claim("userId", userId)
                .claim("userName", userName)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }
    public static Long getUserId(String token) {
        if(StringUtils.isEmpty(token)) return null;
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        Integer userId = (Integer)claims.get("userId");
        return userId.longValue();
    }
    public static String getUserName(String token) {
        if(StringUtils.isEmpty(token)) return "";
        Jws<Claims> claimsJws
                = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("userName");
    }
    
    public static void main(String[] args) {
        String token = JwtHelper.createToken(1L, "55");
        System.out.println(token);
        System.out.println(JwtHelper.getUserId(token));
        System.out.println(JwtHelper.getUserName(token));
    }
}

完善UserInfoServiceImpl登录方法

@Override
public Map<String, Object> login(LoginVo loginVo) {
   .........

    //jwt生成token字符串
    String token = JwtHelper.createToken(userInfo.getId(), name);
    map.put("token",token);

    return map;
}

使用Swagger测试接口,测试多次,第一次注册,以后直接登录
在这里插入图片描述

4、单点登录总结

单点登录
1.session复制|广播的:缺点:比较消耗公司的网络带宽
2.redis统一保存各个微服务的session信息:缺点:单独redis服务
3.token方式:是服务器端根据一定规则[算法+盐值]生成的用户的唯一标识,token不在服务器保存,在浏览器端保存

五、整合阿里云服务短信(云市场)

可以参考下面这篇文章
https://donglin.blog.csdn.net/article/details/125699790

1、新建短信微服务

1、在service模块下创建子模块service_sms
2、创建controller和service代码
3、配置application.properties

# 服务端口
server.port=8206
# 服务名
spring.application.name=service-sms

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000

spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
#最小空闲
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

4、创建启动类
创建ServiceMsmApplication.java

@ComponentScan({"com.donglin"})
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
public class ServiceSmsApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceSmsApplication.class, args);
    }
}

2、编写发送短信接口

1、在service-sms的pom中引入依赖

   <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
       <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
    </dependencies>

2、编写controller,根据手机号发送短信

@RestController
@RequestMapping("/user/sms")
public class SmsController {

    @Autowired
    private SmsService smsService;

    @GetMapping("/send/{phone}")
    public R sendCode(@PathVariable String phone){
        boolean flag=smsService.sendCode(phone);
        if(flag){
            return R.ok();
        }else{
            return R.error();
        }
    }
}

3、编写service

import com.donglin.yygh.sms.service.SmsService;
import com.donglin.yygh.sms.uitls.HttpUtils;
import com.donglin.yygh.sms.uitls.RandomUtil;
import org.apache.http.HttpResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class SmsServiceImpl implements SmsService {

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public boolean sendCode(String phone) {
        String host = "http://dingxin.market.alicloudapi.com";
        String path = "/dx/sendSms";
        String method = "POST";
        String appcode = "f5e58dd4cd56493581466733a71b6c31";
        Map<String, String> headers = new HashMap<String, String>();
        //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
        headers.put("Authorization", "APPCODE " + appcode);
        Map<String, String> querys = new HashMap<String, String>();
        querys.put("mobile", phone);
        String fourBitRandom = RandomUtil.getFourBitRandom();
        querys.put("param", "code:"+fourBitRandom);
        querys.put("tpl_id", "TP1711063");
        Map<String, String> bodys = new HashMap<String, String>();


        try {
            /**
             * 重要提示如下:
             * HttpUtils请从
             * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
             * 下载
             *
             * 相应的依赖请参照
             * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
             */
            HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
            System.out.println(response.toString());
            //获取response的body
            //System.out.println(EntityUtils.toString(response.getEntity()));

            //把验证码保存redis中一份
            redisTemplate.opsForValue().set(phone,fourBitRandom,10, TimeUnit.DAYS);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }


}

4.记得配置网关
在这里插入图片描述

3、完善登录接口

修改UserInfoServiceImpl

@Autowired
private RedisTemplate<String, String> redisTemplate;

//校验校验验证码
String mobleCode = redisTemplate.opsForValue().get(phone);
if(!code.equals(mobleCode)) {
    throw new YyghException(20001,"验证码失败");
}

六、手机验证码登录前端

1、用户登录前端

1、封装api方法
(1)创建api/userInfo.js

import request from '@/utils/request'

const api_name = `/user/userinfo`

export default {
    login(userInfo) {
        return request({
            url: `${api_name}/login`,
            method: `post`,
            data: userInfo
        })
    }
}

(2)创建api/sms.js

import request from '@/utils/request'

const api_name = `/user/sms`

export default {
    sendCode(mobile) {
        return request({
            url: `${api_name}/send/${mobile}`,
            method: `get`
        })
    }
}

2、添加登录组件
登录层是一个公共层,因此我们把它放在头部组件里面
修改layouts/myheader.vue文件

<template>
  <div class="header-container">
      <div class="wrapper">
      <!-- logo -->
          <div class="left-wrapper v-link selected">
              <img style="width: 50px" width="50" height="50" src="~assets/images/logo.png">
              <span class="text">尚医通 预约挂号统一平台</span>
          </div>
      <!-- 右侧 -->
      <div class="right-wrapper">
        <span class="v-link clickable">帮助中心</span>
        <span v-if="name == ''" class="v-link clickable" @click="showLogin()" id="loginDialog">登录/注册</span>
        <el-dropdown v-if="name != ''" @command="loginMenu">
              <span class="el-dropdown-link">
                {{ name }}<i class="el-icon-arrow-down el-icon--right"></i>
              </span>
          <el-dropdown-menu class="user-name-wrapper" slot="dropdown">
            <el-dropdown-item command="/user">实名认证</el-dropdown-item>
            <el-dropdown-item command="/order">挂号订单</el-dropdown-item>
            <el-dropdown-item command="/patient">就诊人管理</el-dropdown-item>
            <el-dropdown-item command="/logout" divided>退出登录</el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>
      </div>
      </div>

      <!-- 登录弹出层 -->
  <el-dialog :visible.sync="dialogUserFormVisible" style="text-align: left;" top="50px" :append-to-body="true"  width="960px" @close="closeDialog()">
    <div class="container">

      <!-- 手机登录 #start -->
      <div class="operate-view" v-if="dialogAtrr.showLoginType === 'phone'">
        <div class="wrapper" style="width: 100%">
          <div class="mobile-wrapper" style="position: static;width: 70%">
            <span class="title">{{ dialogAtrr.labelTips }}</span>
            <el-form>
              <el-form-item>
                <el-input v-model="dialogAtrr.inputValue" :placeholder="dialogAtrr.placeholder" :maxlength="dialogAtrr.maxlength" class="input v-input">
                  <span slot="suffix" class="sendText v-link" v-if="dialogAtrr.second > 0">{{ dialogAtrr.second }}s </span>
                  <span slot="suffix" class="sendText v-link highlight clickable selected" v-if="dialogAtrr.second == 0" @click="getCodeFun()">重新发送 </span>
                </el-input>
              </el-form-item>
            </el-form>
            <div class="send-button v-button" @click="btnClick()"> {{ dialogAtrr.loginBtn }}</div>
          </div>
          <div class="bottom">
            <div class="wechat-wrapper" @click="weixinLogin()"><span
              class="iconfont icon"></span></div>
            <span class="third-text"> 第三方账号登录 </span></div>
        </div>
      </div>
      <!-- 手机登录 #end -->

      <!-- 微信登录 #start -->
      <div class="operate-view"  v-if="dialogAtrr.showLoginType === 'weixin'" >
        <div class="wrapper wechat" style="height: 400px">
          <div>
            <div id="weixinLogin"></div>
          </div>
          <div class="bottom wechat" style="margin-top: -80px;">
            <div class="phone-container">
              <div class="phone-wrapper"  @click="phoneLogin()"><span
                class="iconfont icon"></span></div>
              <span class="third-text"> 手机短信验证码登录 </span></div>
          </div>
        </div>
      </div>
      <!-- 微信登录 #end -->

      <div class="info-wrapper">
        <div class="code-wrapper">
          <div><img src="//img.114yygh.com/static/web/code_login_wechat.png" class="code-img">
            <div class="code-text"><span class="iconfont icon"></span>微信扫一扫关注
            </div>
            <div class="code-text"> “快速预约挂号”</div>
          </div>
          <div class="wechat-code-wrapper"><img
            src="//img.114yygh.com/static/web/code_app.png"
            class="code-img">
            <div class="code-text"> 扫一扫下载</div>
            <div class="code-text"> “预约挂号”APP</div>
          </div>
        </div>
        <div class="slogan">
          <div>xxxxxx官方指定平台</div>
          <div>快速挂号 安全放心</div>
        </div>
      </div>
    </div>
  </el-dialog>
  </div>
</template>
<script>
import cookie from 'js-cookie'
import Vue from 'vue'
import userInfoApi from '@/api/userInfo'
import smsApi from '@/api/sms'

const defaultDialogAtrr = {
showLoginType: 'phone', // 控制手机登录与微信登录切换

labelTips: '手机号码', // 输入框提示

inputValue: '', // 输入框绑定对象
placeholder: '请输入您的手机号', // 输入框placeholder
maxlength: 11, // 输入框长度控制

loginBtn: '获取验证码', // 登录按钮或获取验证码按钮文本

sending: true,      // 是否可以发送验证码
second: -1,        // 倒计时间  second>0 : 显示倒计时 second=0 :重新发送 second=-1 :什么都不显示
clearSmsTime: null  // 倒计时定时任务引用 关闭登录层清除定时任务
}
export default {
data() {
  return {
    userInfo: {
      phone: '',
      code: '',
      openid: ''
    },

    dialogUserFormVisible: false,
    // 弹出层相关属性
    dialogAtrr:defaultDialogAtrr,

    name: '' // 用户登录显示的名称
  }
},

created() {
  this.showInfo()
},

mounted() {
  // 注册全局登录事件对象
  window.loginEvent = new Vue();
  // 监听登录事件
  loginEvent.$on('loginDialogEvent', function () {
    document.getElementById("loginDialog").click();
  })
  // 触发事件,显示登录层:loginEvent.$emit('loginDialogEvent')
},

methods: {
  // 绑定登录或获取验证码按钮
  btnClick() {
    // 判断是获取验证码还是登录
    if(this.dialogAtrr.loginBtn == '获取验证码') {
      this.userInfo.phone = this.dialogAtrr.inputValue

      // 获取验证码
      this.getCodeFun()
    } else {
      // 登录
      this.login()
    }
  },

  // 绑定登录,点击显示登录层
  showLogin() {
    this.dialogUserFormVisible = true

    // 初始化登录层相关参数
    this.dialogAtrr = { ...defaultDialogAtrr }
  },

  // 登录
  login() {
    this.userInfo.code = this.dialogAtrr.inputValue

    if(this.dialogAtrr.loginBtn == '正在提交...') {
      this.$message.error('重复提交')
      return;
    }
    if (this.userInfo.code == '') {
      this.$message.error('验证码必须输入')
      return;
    }
    if (this.userInfo.code.length != 4) {
      this.$message.error('验证码格式不正确')
      return;
    }
    this.dialogAtrr.loginBtn = '正在提交...'
    userInfoApi.login(this.userInfo).then(response => {
      console.log(response.data)
      // 登录成功 设置cookie
      this.setCookies(response.data.name, response.data.token)
    }).catch(e => {
      this.dialogAtrr.loginBtn = '马上登录'
    })
  },

  setCookies(name, token) {
    cookie.set('token', token, { domain: 'localhost' })
    cookie.set('name', name, { domain: 'localhost' })
    window.location.reload()
  },

  // 获取验证码
  getCodeFun() {
    if (!(/^1[34578]\d{9}$/.test(this.userInfo.phone))) {
      this.$message.error('手机号码不正确')
      return;
    }

    // 初始化验证码相关属性
    this.dialogAtrr.inputValue = ''
    this.dialogAtrr.placeholder = '请输入验证码'
    this.dialogAtrr.maxlength = 6
    this.dialogAtrr.loginBtn = '马上登录'

    // 控制重复发送
    if (!this.dialogAtrr.sending) return;

    // 发送短信验证码
    this.timeDown();
    this.dialogAtrr.sending = false;
    smsApi.sendCode(this.userInfo.phone).then(response => {
      this.timeDown();
    }).catch(e => {
      this.$message.error('发送失败,重新发送')
      // 发送失败,回到重新获取验证码界面
      //this.showLogin()
    })
  },

  // 倒计时
  timeDown() {
    if(this.clearSmsTime) {
      clearInterval(this.clearSmsTime);
    }
    this.dialogAtrr.second = 60;

    this.dialogAtrr.labelTips = '验证码已发送至' + this.userInfo.phone
    this.clearSmsTime = setInterval(() => {
      --this.dialogAtrr.second;
      if (this.dialogAtrr.second < 1) {
        clearInterval(this.clearSmsTime);
        this.dialogAtrr.sending = true;
        this.dialogAtrr.second = 0;
      }
    }, 1000);
  },

  // 关闭登录层
  closeDialog() {
    if(this.clearSmsTime) {
      clearInterval(this.clearSmsTime);
    }
  },

  showInfo() {
    let token = cookie.get('token')
    if (token) {
      this.name = cookie.get('name')
      console.log(this.name)
    }
  },

  loginMenu(command) {
    if('/logout' == command) {
      cookie.set('name', '', {domain: 'localhost'})
      cookie.set('token', '', {domain: 'localhost'})

      //跳转页面
      window.location.href = '/'
    } else {
      window.location.href = command
    }
  },

  handleSelect(item) {
    window.location.href = '/hospital/' + item.hoscode
  },

  weixinLogin() {
    this.dialogAtrr.showLoginType = 'weixin'
  },

  phoneLogin() {
    this.dialogAtrr.showLoginType = 'phone'
    this.showLogin()
  }
}
}
</script>

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

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

相关文章

Minecraft服务端配置

✨✨前言 ✨✨ 我的世界大家肯定都不陌生&#xff0c;在网易拿下中国区的代理后&#xff0c;很多小伙伴也是都转向了网易版我的世界&#xff0c;网易版我的世界可以说已经做是的十分全面了&#xff0c;使用起来也十分方便&#xff0c;一部分小伙伴也是看重了网易庞大的玩家数量…

使用uniapp创建小程序和H5界面

uniapp的介绍可以看官网&#xff0c;接下来我们使用uniapp创建小程序和H5界面&#xff0c;其他小程序也是可以的&#xff0c;只演示创建这2个&#xff0c;其实都是一套代码&#xff0c;只是生成的方式不一样而已。 uni-app官网 1.打开HBuilder X 选择如图所示&#xff0c;下…

1. Unity的下载与安装

1. 下载 Unity Hub: unity hub是unity编辑器的一个管理工具&#xff0c;负责平时的unity项目创建和管理&#xff0c;以及unity编辑器的安装等 首先在unity官网网址链接&#xff0c;点击左下角的DownLoad Unity图标&#xff0c;如下图&#xff1a; 进入下一个页面&#xff0c;…

LinkedHashMap实现LRU算法

目录LRU 简介LinkedHashMap的使用手写LRU缓存淘汰算法LRU 简介 LRU 是 Least Recently Used 的缩写&#xff0c;这种算法认为最近使用的数据是热门数据&#xff0c;下一次很大概率将会再次被使用。而最近很少被使用的数据&#xff0c;很大概率下一次不再用到。当缓存容量的满时…

show profile和trance分析SQL

目录 一.show profile分析SQL 二.trance分析优化器执行计划 一.show profile分析SQL Mysql从5.0.37版本开始增加了对show profiles和show profile语句的支持。show profiles能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。。 通过have_profiling参数&#xff0c;能够…

J东滑块分析

内容仅供参考学习 欢迎朋友们V一起交流&#xff1a; zcxl7_7 目标 网址&#xff1a;案例地址 J东登录页面会有滑块&#xff0c;直接用来研究 分析 模拟一次触发滑块验证请求(如图) 有2个重要请求&#xff0c;一个是g.html&#xff0c;一个是s.html。其中很明确的就是g是获…

【100个 Unity实用技能】 | Unity 通过自定义菜单将资源导出

Unity 小科普 老规矩&#xff0c;先介绍一下 Unity 的科普小知识&#xff1a; Unity是 实时3D互动内容创作和运营平台 。包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者&#xff0c;借助 Unity 将创意变成现实。Unity 平台提供一整套完善的软件解决方案&#xff…

C++——二叉树的前序遍历||中序遍历||后序遍历 非递归算法

目录二叉树的前序遍历&#xff0c;非递归迭代实现二叉树的中序遍历 &#xff0c;非递归迭代实现二叉树的后序遍历 &#xff0c;非递归迭代实现二叉树的前序遍历&#xff0c;非递归迭代实现 题目链接 思路&#xff1a; 将任何一颗树分成两个部分&#xff0c;一部分是左路节点&a…

用Three.js搭建的一个艺术场景

本文翻译自于Medium&#xff0c;原作者用 Three.js 创建了一个“Synthwave 场景”&#xff0c;效果还不错&#xff0c;在此加上自己的理解&#xff0c;记录一下。在线Demo. 地形构建 作者想要搭建一个中间平坦、两侧有凹凸山脉效果并且能够一直绵延不断的地形&#xff0c;接下…

Quartz组件任务调度管理

Quartz什么是Quartzquartz:石英钟的意思是一个当今市面上流行的高效的任务调度管理工具所谓"调度"就是制定好的什么时间做什么事情的计划由OpenSymphony开源组织开发Symphony:交响乐是java编写的,我们使用时需要导入依赖即可为什么需要Quartz所谓"调度"就是…

18:CTK 总结篇(FAQ)

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 经过了几个月的艰苦奋战,终于到了最后一节啦,是不是和我一样,心里有点儿小激动! 回顾之前的章节,从初级 -> 进阶 -> 高级,我们针对 CTK 做了详细的分类讲解。希望通过这些知识,大家能对模块化…

管理会计报告和财务报告的区别

财务会计报告是给投资人看的&#xff0c;可以反映公司总体的盈利能力。不过&#xff0c;我们回顾一下前面“第一天”里面提到的问题。如果你是公司的产品经理&#xff0c;目前有三个产品在你的管辖范围内。上级给你一笔新的资金&#xff0c;这笔资金应该投到哪个产品上&#xf…

c++容器

1、vector容器 1.1性质 a&#xff09;该容器的数据结构和数组相似&#xff0c;被称为单端数组。 b&#xff09;在存储数据时不是在原有空间上往后拓展&#xff0c;而是找到一个新的空间&#xff0c;将原数据深拷贝到新空间&#xff0c;释放原空间。该过程被称为动态拓展。 vec…

什么是猜疑心理?小猫测试网科普小作文

什么是猜疑心理&#xff1f;猜疑心理是说一个人心中想法偏离了客观事实&#xff0c;牵强附会&#xff0c;往往是指不好的一面&#xff0c;对别人的一言一行都充满了不良的解读&#xff0c;认为这些对自己都有针对性&#xff0c;目的性&#xff0c;对自己都是不利的。猜疑心理重…

算力引领 数“聚”韶关——第二届中国韶关大数据创新创业大赛圆满收官

为进一步促进数字经济领域创新创业发展&#xff0c;推动国家数据中心集群建设&#xff0c;构建大数据领域资源专业平台&#xff0c;促进大湾区大数据科技成果和创新创业人才转化落地&#xff0c;为韶关大数据领域创新型产业集群的打造、大数据科技成果和创新创业人才的转化落地…

如何选择合适的固态继电器?

如何选择合适的固态继电器&#xff1f; 在选择固态继电器&#xff08;SSR&#xff09;时&#xff0c;应根据实际应用条件和SSR性能参数&#xff0c;特别要考虑到使用中的过流和过压条件以及SSR的负载能力&#xff0c;这有助于实现固态继电器的长寿命和高可靠性。然后&#xff0…

九龙证券|最新评级情况出炉,机构扎堆这一板块!聚氨酯龙头获得最多关注

本周算计254家上市公司获组织“买入型”评级。 电子板块评级组织扎堆 证券时报数据宝计算&#xff0c;2月13日至17日&#xff0c;A股市场53家组织算计进行347次评级&#xff0c;254家上市公司获“买入型”评级&#xff08;包含买入、增持、强烈推荐、推荐&#xff09;。 从申…

ONNX yolov5导出 convert error --grid

使用的版本 https://github.com/ultralytics/yolov5/tree/v5.0 安装onnx torch.onnx.export(model, img, f, verboseFalse, opset_version12, input_names[images],output_names[classes, boxes] if y is None else [output],dynamic_axes{images: {0: batch, 2: height, 3:…

智慧校园:校务助手微信小程序端源码

校园校务助手-智慧校园教师移动端校园校务助手微信小程序端也指智慧校园教师端微信小程序 它包括哪些功能呢&#xff1f;我来介绍一下。 智慧校园教师端微信小程序是基于原生开发 教师端登录界面①.设备管理、通知管理、图片管理、班级考勤 ②.综合素质评价、视频管理、请假…

SQL Server 2008新特性——更改跟踪

在大型的数据库应用中&#xff0c;经常会遇到部分数据的脱机和多个数据库的合并问题。比如现在有一个全省范围使用的应用程序&#xff0c;每个市都部署了单独的相同的应用程序服务器和数据库服务器&#xff0c;每个月需要将全省所有市的数据全部汇总起来用于出全省的报表&#…