• Google验证码,扫描绑定,SpringBoot+ vue


    后端

    1.使用Google工具类

    package com.ruoyi.common.utils.googleAuth;
    import org.apache.commons.codec.binary.Base32;
    import org.apache.commons.codec.binary.Base64;
    
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import java.security.SecureRandom;
    
    public class GoogleGenerator {
    
        // 生成的key长度( Generate secret key length)
        public static final int SECRET_SIZE = 10;
    
        public static final String SEED = "22150146801713967E8g";
        // Java实现随机数算法
        public static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";
        // 最多可偏移的时间
        int window_size = 3; // default 3 - max 17
    
        public static String generateSecretKey() {
            SecureRandom sr;
            try {
                sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);
                sr.setSeed(Base64.decodeBase64(SEED));
                byte[] buffer = sr.generateSeed(SECRET_SIZE);
                Base32 codec = new Base32();
                byte[] bEncodedKey = codec.encode(buffer);
                return new String(bEncodedKey);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 这个format不可以修改,身份验证器无法识别二维码
         */
        public static String getQRBarcode(String user, String secret) {
            String format = "otpauth://totp/%s?secret=%s";
            return String.format(format, user, secret);
        }
    
        /**
         * 根据user和secret生成二维码的密钥
         */
        public static String getQRBarcodeURL(String user, String host, String secret) {
            String format = "http://www.google.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl=otpauth://totp/%s@%s?secret=%s";
            return String.format(format, user, host, secret);
        }
    
        public boolean check_code(String secret, String code, long timeMsec) {
            Base32 codec = new Base32();
            byte[] decodedKey = codec.decode(secret);
            long t = (timeMsec / 1000L) / 30L;
            for (int i = -window_size; i <= window_size; ++i) {
                long hash;
                try {
                    hash = verify_code(decodedKey, t + i);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException(e.getMessage());
                }
                System.out.println("code=" + code);
                System.out.println("hash=" + hash);
                if (code.equals(addZero(hash))) {
                    return true;
                }
            }
            return false;
        }
    
        private static int verify_code(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
            byte[] data = new byte[8];
            long value = t;
            for (int i = 8; i-- > 0; value >>>= 8) {
                data[i] = (byte) value;
            }
            SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(signKey);
            byte[] hash = mac.doFinal(data);
            int offset = hash[20 - 1] & 0xF;
            long truncatedHash = 0;
            for (int i = 0; i < 4; ++i) {
                truncatedHash <<= 8;
                truncatedHash |= (hash[offset + i] & 0xFF);
            }
            truncatedHash &= 0x7FFFFFFF;
            truncatedHash %= 1000000;
            return (int) truncatedHash;
        }
    
        private String addZero(long code) {
            return String.format("%06d", code);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    这个 类的 verifyTest 方法可以判断扫描绑定之后的app上面验证码的准确性。

    package com.ruoyi.common.utils.googleAuth;
    
    /**
     *
     *
     * 身份认证测试
     *
     * @author yangbo
     *
     * @version 创建时间:2017年8月14日 上午11:09:23
     *
     *
     */
    public class GoogleUtils {
    
    
        //@Test
        public String genSecret(String g_name) {// 生成密钥
            String secret = GoogleGenerator.generateSecretKey();
            // 把这个qrcode生成二维码,用google身份验证器扫描二维码就能添加成功
            String qrcode = GoogleGenerator.getQRBarcode(g_name, secret);
            System.out.println("qrcode:" + qrcode + ",key:" + secret);
    
            return secret;
        }
        /**
         * 对app的随机生成的code,输入并验证
         */
    
        public static boolean verifyTest(String code,String secret) {
            long t = System.currentTimeMillis();
            GoogleGenerator ga = new GoogleGenerator();
           // ga.setWindowSize(5);
            boolean r = ga.check_code(secret, code, t);
            System.out.println("检查code是否正确?" + r);
            return r;
        }
    
        public static void main(String [] args)
        {
            GoogleUtils gt=new GoogleUtils();
    
            String secret=gt.genSecret("Antpay(web3game)");
            verifyTest("Antpay(web3game)","2DYHBGQLNLQWSPZV");
    
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    这个类通过g_user,g_code(就是谷歌验证器的secret,这个你已经插入到数据库 中)来生成相关二维码。

    package com.ruoyi.common.utils.googleAuth;
    
    import com.google.zxing.BarcodeFormat;
    import com.google.zxing.EncodeHintType;
    import com.google.zxing.MultiFormatWriter;
    import com.google.zxing.WriterException;
    import com.google.zxing.common.BitMatrix;
    import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 二维码工具类
     */
    public class QRCodeUtil {
        /**
         * 生成二维码
         *
         * @param content 二维码的内容
         * @return BitMatrix对象
         */
        public static BitMatrix createCode(String content) {
            //二维码的宽高
            int width = 200;
            int height = 200;
    
            //其他参数,如字符集编码
            Map<EncodeHintType, Object> hints = new HashMap<>();
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            //容错级别为H
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
            //白边的宽度,可取0~4
            hints.put(EncodeHintType.MARGIN, 0);
    
            BitMatrix bitMatrix = null;
            try {
                //生成矩阵,因为我的业务场景传来的是编码之后的URL,所以先解码
                bitMatrix = new MultiFormatWriter().encode(content,
                        BarcodeFormat.QR_CODE, width, height, hints);
    
                //bitMatrix = deleteWhite(bitMatrix);
            } catch (WriterException e) {
                e.printStackTrace();
            }
    
            return bitMatrix;
        }
    
        /**
         * 删除生成的二维码周围的白边,根据审美决定是否删除
         *
         * @param matrix BitMatrix对象
         * @return BitMatrix对象
         */
        private static BitMatrix deleteWhite(BitMatrix matrix) {
            int[] rec = matrix.getEnclosingRectangle();
            int resWidth = rec[2] + 1;
            int resHeight = rec[3] + 1;
    
            BitMatrix resMatrix = new BitMatrix(resWidth, resHeight);
            resMatrix.clear();
            for (int i = 0; i < resWidth; i++) {
                for (int j = 0; j < resHeight; j++) {
                    if (matrix.get(i + rec[0], j + rec[1]))
                        resMatrix.set(i, j);
                }
            }
            return resMatrix;
        }
    }
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    2.用工具类自带的g_user,g_code来生成二维码

    这个是SysUser数据库表的部分g_user,g_code数据。

    g_userg_code
    Antpay(admin)PXUPGNVY6QPWRNNQ
    Antpay(payUser)LLFS2ON52UAXOIKP
    Antpay(shanghu4)DYFTPDY5MS7CS3HA
    Antpay(hwgame)AKZQA7ANHHHZ5TQW
    Antpay(baby)5GTBWBTRPEYWCSW2
    Antpay(beartech)WEPHOIBAQACJ7VNP
    Antpay(gmoney)3AZTCIQJAZMV6IGK
    Antpay(ml)H45DLW4C37QNVUX5
    Antpay(ml_afr)4XOGTVG7AJXMPJBQ
    Antpay(agent1)J6TCF3TIWYC57WWE
    Antpay(10069)MKIC4KXOSIU6H2OC
    Antpay(10071)7VHKY4YIWCSDBYEC
    Antpay(M10068)JKBGRRXBFSQGX45Q
    Antpay(M1006801)FI2TNSP2PYOWZKVX
    Antpay(M1006802)OTTHGUQHFYNAHDMV

    2.1通过请求来生成相关二维码,后端返回给前端

    package com.ruoyi.web.controller.runscore;
    
    
    import com.google.zxing.client.j2se.MatrixToImageWriter;
    import com.google.zxing.common.BitMatrix;
    import com.ruoyi.common.annotation.Anonymous;
    import com.ruoyi.common.core.controller.BaseController;
    import com.ruoyi.common.core.domain.AjaxResult;
    import com.ruoyi.common.core.domain.entity.SysUser;
    import com.ruoyi.common.utils.googleAuth.GoogleGenerator;
    import com.ruoyi.common.utils.googleAuth.QRCodeUtil;
    import com.ruoyi.system.service.ISysUserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.OutputStream;
    @Anonymous
    @RestController
    @RequestMapping(value = "/googleAuth")
    public class GoogleAuthController extends BaseController {
        @Autowired
        private ISysUserService sysUserService;
    
        //根据user和secret生成二维码的密钥
        @PostMapping(value = "/getQRBarcodeURL")
         public AjaxResult getQRBarcodeURL(String user, String host, String secret) {
            return success(GoogleGenerator.getQRBarcodeURL(user, host, secret));
        }
        //查看google 二维码信息
        @PostMapping(value = "/getQRBarcode")
         public AjaxResult getQRBarcode(String user, String secret) {
            return success(GoogleGenerator.getQRBarcode(user, secret));
        }
    
        /**
         * 生成二维码
         */
        @GetMapping(value = "/generateQRCode/{userId}")
         public void GenerateQRCode(String content, @PathVariable("userId") String userId, HttpServletResponse response) throws IOException {
    /*        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            if ("anonymousUser".equals(principal)) {
                return ;
            }
            LoginUser user = (LoginUser) principal;*/
    //        UserAccountInfoVO userAccountInfo = userAccountService.getUserAccountInfo(user.getUserAccountId());
    
       /*     LoginUser user = SecurityUtils.getLoginUser();
            String userId = SecurityUtils.getUserId();*/
            SysUser user = sysUserService.selectUserById(userId);
            content=GoogleGenerator.getQRBarcode(user.getGoogleUser(),user.getGoogleCode());
    //        content=GoogleGenerator.getQRBarcode("(gemblastmaster)","RGOEVUN2G44TTRZT");
            // 设置响应流信息
            response.setContentType("image/jpg");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
            OutputStream stream = response.getOutputStream();
            //获取一个二维码图片
            BitMatrix bitMatrix = QRCodeUtil.createCode(content);
            //以流的形式输出到前端
            MatrixToImageWriter.writeToStream(bitMatrix, "jpg", stream);
        }
        //新增用户的时候生成密钥并且保存
        @GetMapping(value = "/geSecretKey")
         public AjaxResult geSecretKey() {
            return success(GoogleGenerator.generateSecretKey());
        }
    
        //验证code是否合法
        @PostMapping(value = "/checkValidCode")
        public AjaxResult checkGoogleValidCode(String secret, String code) {
            return success(new GoogleGenerator().check_code(secret, code, System.currentTimeMillis()));
        }
    
    }
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    
        private boolean isMatchMerchant(String username, String googleCode) {
            if(StringUtils.isEmpty(googleCode)){
                return false;
            }
    
            SysUser sysUser = userService.selectUserByUserName(username);
            if(Objects.isNull(sysUser)){
                throw new RuntimeException("不存在这个用户!");
            }
         /*   QueryWrapper queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("relevance_account_id", sysUser.getUserId());
            Merchant merchant = merchantRepo.selectOne(queryWrapper);*/
           Merchant merchant = merchantRepo.selectByUserId(sysUser.getUserId());
            if (Objects.isNull(merchant)) {
                return false;
            }
            if(googleCode.equals(merchant.getSecretKey()) || GoogleUtils.verifyTest(googleCode, sysUser.getGoogleCode())){
                return true;
            }
    
            return false;
        }
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    3.第一次通过生成的secret_key登录,之后扫描进行绑定Google验证码,通过验证码进行登录

    前端Vue

    <template>
      <div class="login">
        <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
          <h3 class="title">登 录h3>
          <el-form-item prop="username">
            <el-input
              v-model="loginForm.username"
              type="text"
              auto-complete="off"
              placeholder="账号"
            >
              <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
            el-input>
          el-form-item>
          <el-form-item prop="password">
            <el-input
              v-model="loginForm.password"
              type="password"
              auto-complete="off"
              placeholder="密码"
              @keyup.enter.native="handleLogin"
            >
              <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
            el-input>
          el-form-item>
          <el-form-item prop="code">
            <el-input
              v-model="loginForm.code"
              type="text"
              placeholder="Google验证码"
              @keyup.enter.native="handleLogin"
            >
              
            el-input>
          el-form-item>
          
          <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码el-checkbox> <br>
          <el-button type="text" @click="showCode" >扫码关联Google验证器 el-button>
          <el-form-item style="width:100%;">
            <el-button
              :loading="loading"
              size="medium"
              type="primary"
              style="width:100%;"
              @click.native.prevent="handleLogin"
            >
              <span v-if="!loading">登 录span>
              <span v-else>登 录 中...span>
            el-button>
            <div style="float: right;" v-if="register">
              <router-link class="link-type" :to="'/register'">立即注册router-link>
            div>
          el-form-item>
        el-form>
        
        <div class="el-login-footer">
          <span>Copyright © 2018-2024 antcash.vip All Rights Reserved.span>
        div>
      div>
    template>
    
    <script>
    import { getCodeImg, login, showQRCode} from "@/api/login";
    import Cookies from "js-cookie";
    import { encrypt, decrypt } from '@/utils/jsencrypt'
    
    export default {
      name: "Login",
      data() {
        return {
    
          codeUrl: "",
          loginForm: {
            username: "admin",
            password: "admin123",
            rememberMe: false,
            googleCode: "",
            code: "",
            uuid: ""
          },
          loginRules: {
            username: [
              { required: true, trigger: "blur", message: "请输入您的账号" }
            ],
            password: [
              { required: true, trigger: "blur", message: "请输入您的密码" }
            ],
            // code: [{ required: true, trigger: "change", message: "请输入验证码" }],
            code: [{ required: true, trigger: "blur", message: "请输入google验证码" }],
          },
          loading: false,
          // 验证码开关
          captchaEnabled: false,
          // 注册开关
          register: false,
          redirect: undefined
        };
      },
      watch: {
        $route: {
          handler: function(route) {
            this.redirect = route.query && route.query.redirect;
          },
          immediate: true
        }
      },
      created() {
        // this.getCode();
        this.getCookie();
      },
      methods: {
        // getCode() {
        //   getCodeImg().then(res => {
        //     this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
        //     if (this.captchaEnabled) {
        //       this.codeUrl = "data:image/gif;base64," + res.img;
        //       this.loginForm.uuid = res.uuid;
        //     }
        //   });
        // },
    
        showCode () {
    
                if (this.loginForm.username == null || this.loginForm.username == '') {
                  this.$message.error('请输入用户名');
                    return;
                }
                if (this.loginForm.password == null || this.loginForm.password == '') {
                  this.$message.error('请输入密码');
                    return;
                }
                if (this.loginForm.code == null || this.loginForm.code == '') {
                  this.$message.error('请输入google验证码');
                    return;
                }
              let  username1  = this.loginForm.username;
              let     password1 = this.loginForm.password;
              let    code1 = this.loginForm.code;
              login(username1, password1, code1).then(res =>{
            //  showQRCode(res.userId).then();
        let url='http://localhost/dev-api/googleAuth/generateQRCode/'+ res.userId;
        window.open(url, '_blank');
                });
            },
        getCookie() {
          const username = Cookies.get("username");
          const password = Cookies.get("password");
          const rememberMe = Cookies.get('rememberMe')
          const code = Cookies.get('googleCode')
          this.loginForm = {
            username: username === undefined ? this.loginForm.username : username,
            password: password === undefined ? this.loginForm.password : decrypt(password),
            rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
            // code: code === undefined ? this.loginForm.code : code,
          };
        },
        handleLogin() {
          this.$refs.loginForm.validate(valid => {
            if (valid) {
              this.loading = true;
              if (this.loginForm.rememberMe) {
                Cookies.set("username", this.loginForm.username, { expires: 30 });
                Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
                Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
              } else {
                Cookies.remove("username");
                Cookies.remove("password");
                Cookies.remove('rememberMe');
                // Cookies.remove('googleCode');
              }
              this.$store.dispatch("Login", this.loginForm).then(() => {
                this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
              }).catch(() => {
                this.loading = false;
                if (this.captchaEnabled) {
                  this.getCode();
                }
              });
            }
          });
        }
      }
    };
    script>
    
    <style rel="stylesheet/scss" lang="scss">
    .login {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100%;
      background-image: url("../assets/images/login-background.jpg");
      background-size: cover;
    }
    .title {
      margin: 0px auto 30px auto;
      text-align: center;
      color: #707070;
    }
    
    .login-form {
      border-radius: 6px;
      background: #ffffff;
      width: 400px;
      padding: 25px 25px 5px 25px;
      .el-input {
        height: 38px;
        input {
          height: 38px;
        }
      }
      .input-icon {
        height: 39px;
        width: 14px;
        margin-left: 2px;
      }
    }
    .login-tip {
      font-size: 13px;
      text-align: center;
      color: #bfbfbf;
    }
    .login-code {
      width: 33%;
      height: 38px;
      float: right;
      img {
        cursor: pointer;
        vertical-align: middle;
      }
    }
    .el-login-footer {
      height: 40px;
      line-height: 40px;
      position: fixed;
      bottom: 0;
      width: 100%;
      text-align: center;
      color: #fff;
      font-family: Arial;
      font-size: 12px;
      letter-spacing: 1px;
    }
    .login-code-img {
      height: 38px;
    }
    style>
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
  • 相关阅读:
    计算机毕业设计ssm+vue基本微信小程序的体检预约小程序
    df和du命令—查看磁盘和文件大小
    15. Redis 持久化
    COLMAP中将旋转矩阵转为四元数的实现
    Python-日志模块
    四、nginx静态文件的配置
    【第29篇】MAE:屏蔽自编码器是可扩展的视觉学习器
    Java四舍五入
    深度学习| U-Net网络
    Mysql数据库 ---高级操作
  • 原文地址:https://blog.csdn.net/houzhicongone/article/details/136396249