AcWing—SpringBoot【学习笔记】(整合)

项目源代码:https://git.acwing.com/lzy612/kob

文章目录

  • King Of Bots
    • 一、配置Git环境和项目的创建
      • 1、大致的模块
      • 2、Git环境的配置
        • 2.1、为什么要用git
        • 2.2、git的下载
        • 2.3、配置git环境
      • 3、前后端分离
        • 3.1、概念
        • 3.2、King Of Bots是前后端分离
      • 4、创建项目后端
        • 4.1、idea中新建项目
        • 4.2、运行项目
      • 5、创建项目Web端和AcApp端
        • 5.1、下载node.js,安装vue3
        • 5.2、创建vue项目
        • 5.3、修改web
        • 5.4、解决跨域问题
    • 二、创建菜单与游戏界面
      • 1、菜单页面
        • 1.1、导航栏
        • 1.2、页面设计
      • 2、游戏界面
        • 2.1、大致设计
        • 2.2、基类的创建
        • 2.3、地图类
        • 2.4、对战-游戏区域
      • 3、蛇的设计
        • 3.1、蛇的步长变化
        • 3.2、蛇的雏形
    • 三、配置Mysql与注册登录模块
      • 1、配置mysql
        • 1.1、安装myql
        • 1.2、idea中使用mysql
      • 2、配置SpringBoot
        • 2.1、配置pom.xml依赖
        • 2.2、访问数据库配置
        • 2.3、SpringBoot层的概念
        • 2.4、SpringBoot简单调试-查询-插入-删除
        • 2.5、SpringBoot安全访问配置
        • 2.6、SpringBoot密码加密
      • 3、Jwt验证方式(流行版本)
        • 3.1、简单讲述
        • 3.2、配置Jwt
        • 3.3、编写Login的API
        • 3.4、编写Info的API
        • 3.5、编写register的API
      • 4、前端登录页面和注册页面
        • 4.1、登录页面
        • 4.2、前端页面授权
        • 4.3、注册页面
        • 4.4、登录页面持久化
    • 四、创建个人中心页面
      • 1、后端编写API
        • 1.1、建表、pojo、mapper、service接口
        • 1.2、编写AddServiceImpl和AddController
        • 1.3、编写RemoveServiceImpl和RemoveController
        • 1.4、编写UpdateServiceImpl和UpdateController
        • 1.5、编写GetListServiceImpl和GetListController
      • 2、前端个人中心页面实现
        • 2.1、创建大致的页面
        • 2.1、编写添加按钮弹出框
        • 2.2、添加按钮逻辑
        • 2.3、删除按钮逻辑
        • 2.4、修改按钮逻辑
        • 2.5、将代码区域换成编译器
        • 2.6、删除点击确定
    • 五、实现微服务
      • 1、知识点补充
        • 1.1、http协议的实现
        • 1.2、websocket协议的实现
        • 1.3、两者的区别
      • 2、环境的配置和配置类
      • 3、代码编写
        • 3.1、修改后端
        • 3.2、修改前端
        • 3.3、增加JWT验证
        • 3.4、前端页面
      • 4、游戏同步策略
        • 4.1、坐标同步
        • 4.2、多线程修改
        • 4.3、前端结束弹窗
        • 4.4、增加数据库
      • 5、微服务-匹配策略的实现
        • 5.1、创建项目及配置环境
        • 5.2、实现两个项目间通信
        • 5.3、匹配服务的编写
      • 6、实现微服务:Bot代码执行
        • 6.1、创建项目配置依赖
        • 6.2、准备工作
        • 6.3、实现线程
        • 6.4、编写一个AI
        • 6.5、修改天梯积分
    • 六、完成对战列表与排行榜页面
      • 1、实现返回对局列表的api
      • 2、对局列表前端页面修改
      • 3、对局录像回放
      • 4、分页条编写
      • 5、实现返回排行榜数据的api
      • 6、排行榜前端页面修改
      • 7、增加Bot数量上限
    • 七、项目上线
      • 1、服务器环境配置
      • 2、打包后端
      • 3、打包前端
    • 八、实现AcApp端
      • 1、准备工作
      • 2、测试
      • 3、整改前端代码
    • 九、AcApp与Web端的第三方授权登录
      • 1、AcApp端第三方登录代码实现
      • 2、Web端第三方登录代码实现

King Of Bots

一、配置Git环境和项目的创建

1、大致的模块

AcWing—SpringBoot【学习笔记】(整合)_第1张图片

  • 通过页面进行模块的划分,还是通过项目去驱动技术,哪缺啥技术去学什么技术
  • y总这里说聊天框可能实现有时间就实现
  • 这一套后端可以去放到webapp小程序端,通用

2、Git环境的配置

2.1、为什么要用git

  • 存档:用来保存你的代码并且当你遇到一些错误的时候,可以很方便的进行回滚代码,放在云端很安全、方便

  • 同步不同机器上的代码:比用U盘、硬盘更加装B,更加方便,pull一下就都下来了

2.2、git的下载

​ 网址:git下载地址(网上搜教程即可)

2.3、配置git环境

  • 先进入Git Bash,然后进到家目录(cd),输入ssh-keygen生成秘钥,然后进入.ssh目录下,查看id_rsa.pub下的内容,复制下来

  • 然后进入AcGit注册并登录,在SSH秘钥中将复制的内容粘进去,点击添加秘钥即可

  • 选择你的项目位置然后创建好你的目录,专业来说的应该要在项目中创建一个readme.md来做一个介绍,然后在你的项目目录下右键Git Bash here,然后输入git init进行仓库初始化,初始化成功后,应该在文件夹下能看到一个.git隐藏文件,然后git add . 、git commit -m “创建项目”

  • 进入AcGit创建一个新的项目与你在本地项目目录的文件名一致

  • 复制AcGit中的git config 那两段代码(去掉–global)到Git Bash中粘贴,设置好作者名和邮箱

  • 复制git remote add origin [email protected]:xxx/kob.git这段代码到Git Bash中连接好仓库

  • 在Git Bash中输入git push然后将弹出来的代码复制下来并执行,将本地仓库的推送到远程仓库,在远程仓库你的项目看见了readme.md就算成功

    至此,git环境就配置完成了,工欲善其事,必先利其器(其他操作可bing或者报名Linux基础课)

3、前后端分离

3.1、概念

  • 前后端分离:有一个在客户端和服务端来回的请求和渲染
  • 前后端不分离:直接在客户端完成的

3.2、King Of Bots是前后端分离

AcWing—SpringBoot【学习笔记】(整合)_第2张图片

4、创建项目后端

4.1、idea中新建项目

  • 配置Maven3.8.6安装与配置
  • 新建项目

AcWing—SpringBoot【学习笔记】(整合)_第3张图片

这里一定要看好一定要选择Maven仓库,啊!!!!!

  • https://start.spring.io/加载慢的话,可以换成:https://start.aliyun.com

4.2、运行项目

  • 运行src/main/java/com.kob.backend/BackendApplication(第一次进来时候可能需要选择jdk要18/8)
  • 这里我遇到了一些问题就是版本问题和梯子问题,我后来把梯子关了,然后换成了https://start.aliyun.com,然后用了较低的版本等到maven导入完依赖后就可以正常运行了

AcWing—SpringBoot【学习笔记】(整合)_第4张图片

AcWing—SpringBoot【学习笔记】(整合)_第5张图片

  • 成功!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

5、创建项目Web端和AcApp端

5.1、下载node.js,安装vue3

  • 教程地址<—下面有好多大佬的各种安装方面的教程

    AcWing—SpringBoot【学习笔记】(整合)_第6张图片

  • 大佬教程<----这个我认为很不错

5.2、创建vue项目

  • 大佬教程,大佬一站式解决

  • AcWing—SpringBoot【学习笔记】(整合)_第7张图片

  • 这里也有好多问题,我搞了好几个小时,最后是把node.js版本调低了16.15.1,vue用的版本高些5.0多,然后管理员模式进入kob目录下,最后vue ui,创建项目才成功!!!!!!(在kob目录下)

  • 然后点击插件把这两个添加了

    请添加图片描述

  • 去依赖的地方,安装两个依赖一个jquery、一个bootstrap

    请添加图片描述

    请添加图片描述

  • 然后点击任务、serve、运行、运行成功后点击输出,点击那个localhost的网址

    请添加图片描述

  • 出现以下界面就成功了

    AcWing—SpringBoot【学习笔记】(整合)_第8张图片

  • 同理再创建一个acapp在kob目录下

  • 这里只用添加一个vuex

  • 然后git add 加 commit一套操作

5.3、修改web

  • 用vscode打开web文件,修改src/router/index.js文件,将这两个Hash删除掉

    AcWing—SpringBoot【学习笔记】(整合)_第9张图片

  • 这时候点击Home、About来回切换过程中,地址栏中的#就不会出现了

    请添加图片描述

  • 删除一些没用的代码,AboutView.vue、HomeView.vue、HelloWorld.vue

    AcWing—SpringBoot【学习笔记】(整合)_第10张图片

    AcWing—SpringBoot【学习笔记】(整合)_第11张图片

  • 将APP.vue中的style中的代码删除,nav中的代码也删除掉

    AcWing—SpringBoot【学习笔记】(整合)_第12张图片

    AcWing—SpringBoot【学习笔记】(整合)_第13张图片

  • 加一段话div中的hello world

    AcWing—SpringBoot【学习笔记】(整合)_第14张图片

  • 删除index.js中一段代码

    AcWing—SpringBoot【学习笔记】(整合)_第15张图片

  • 最后变成这样干净的页面

    AcWing—SpringBoot【学习笔记】(整合)_第16张图片

5.4、解决跨域问题

  • 跨域问题y总说就是localhost和127.0.01不同,浏览器有个安全机制,不让随便访问,解决办法上网搜代码抄代码

  • App.vue代码如下:

    
    
    
    
    
    
  • 在idea后端的src/main/java/com/kob/backend/config下创建CorsConfig,代码如下(完美解决!)

    package com.kob.backend.config;
    
    import org.springframework.context.annotation.Configuration;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Configuration
    public class CorsConfig implements Filter {
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            HttpServletResponse response = (HttpServletResponse) res;
            HttpServletRequest request = (HttpServletRequest) req;
    
            String origin = request.getHeader("Origin");
            if(origin!=null) {
                response.setHeader("Access-Control-Allow-Origin", origin);
            }
    
            String headers = request.getHeader("Access-Control-Request-Headers");
            if(headers!=null) {
                response.setHeader("Access-Control-Allow-Headers", headers);
                response.setHeader("Access-Control-Expose-Headers", headers);
            }
    
            response.setHeader("Access-Control-Allow-Methods", "*");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Credentials", "true");
    
            chain.doFilter(request, response);
        }
    
        @Override
        public void init(FilterConfig filterConfig) {
    
        }
    
        @Override
        public void destroy() {
        }
    }
    
  • 最后为了美观搞一个背景图片,将背景图片放到web/src/assets目录下,在App.vue下面加上这个(一个是图片的url,一个是图片全覆盖的属性)

    
    

二、创建菜单与游戏界面

1、菜单页面

1.1、导航栏

  • Bootstrap中找一个Navbar和一些下拉菜单就可以了,我用的是以下的

    AcWing—SpringBoot【学习笔记】(整合)_第17张图片

AcWing—SpringBoot【学习笔记】(整合)_第18张图片

<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Navbar w/ texta>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon">span>
    button>
    <div class="collapse navbar-collapse" id="navbarText">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" href="#">Homea>
        li>
        <li class="nav-item">
          <a class="nav-link" href="#">Featuresa>
        li>
        <li class="nav-item">
          <a class="nav-link" href="#">Pricinga>
        li>
      ul>
      <span class="navbar-text">
        Navbar text with an inline element
      span>
    div>
  div>
nav>
  • 然后在web项目下components目录下,创建NavBar.vue组件到NarBar.vue中,将上述导航栏修改成我们需要的然后放到到中,代码如下:
<template>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="#">King Of Botsa>
            <div class="collapse navbar-collapse" id="navbarText">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item">
                <a class="nav-link" aria-current="page" href="#">对战a>
                li>
                <li class="nav-item">
                <a class="nav-link active" href="#">对局列表a>
                li>
                <li class="nav-item">
                <a class="nav-link" href="#">排行榜a>
                li>
            ul>
             <ul class="navbar-nav">
                <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="navbarScrollingDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                            lzy
                        a>
                        <ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
                            <li><a class="dropdown-item" href="#">我的Botsa>li>
                            <li><hr class="dropdown-divider">li>
                            <li><a class="dropdown-item" href="#">退出a>li>
                        ul>
                    li>
            ul>
            div>
        div>
    nav>
template>
<script>
script>
<style scoped>
style>
  • 然后在App.vue中导入依赖和导航栏,使用它们




  • 最后的效果

1.2、页面设计

  • 首先在views文件夹下创建对应页面的包及vue

AcWing—SpringBoot【学习笔记】(整合)_第19张图片

  • 里面的内容大致是这样的,先不写逻辑先就展示一下看看





  • 然后配置router,在index.js中修改
import { createRouter, createWebHistory } from 'vue-router'
import PkIndexView from '../views/pk/PkIndexView';
import RanklistIndexView from '../views/ranklist/RanklistIndexView';
import RecordIndexView from '../views/record/RecordIndexView';
import UserBotIndexView from '../views/user/bot/UserBotIndexView';
import NotFound from '../views/404/NotFound';
const routes = [
  {
    path: "/",
    name: "home",
    redirect: "/pk/"
  },
  {
    path: "/pk/",
    name: "pk_index",
    component: PkIndexView
  },
  {
    path: "/ranklist/",
    name: "ranklist_index",
    component: RanklistIndexView
  },
  {
    path: "/record/",
    name: "record_index",
    component: RecordIndexView
  },
  {
    path: "/user/bot/",
    name: "userbot_index",
    component: UserBotIndexView
  },
  {
    path: "/404/",
    name: "404",
    component: NotFound
  },
  {
    path: "/:catchAll(.*)",
    redirect: "/404/"
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

  • 这样我们就可以在地址栏中输入对应的路径看到对应的页面了

AcWing—SpringBoot【学习笔记】(整合)_第20张图片

  • 然后如果想要点击导航栏中的图标就要就给这些图标绑定上href,在NavBar.vue修改即可

AcWing—SpringBoot【学习笔记】(整合)_第21张图片

  • 这时我们发现每点击一次这个页面就会刷新一下,我们通过修改a标签为router-link可以解决这个问题
    AcWing—SpringBoot【学习笔记】(整合)_第22张图片




  • 给每个页面都加上一个卡片,这样我们发现这么多的页面都需要加卡片,而且他们除了名字以外每个页面都一样,所以我们这里要抽取相同的部分做成一个子组件,当修改相同的部分的时候只需要修改一次其他的页面就都会修改了,然后在每个页面导入这个组件然后加上每个页面的属性即可显示出不同属性的页面了

AcWing—SpringBoot【学习笔记】(整合)_第23张图片







AcWing—SpringBoot【学习笔记】(整合)_第24张图片






  • 给导航栏选择页面时候加上高亮(这里的 : class的“:”加上以后可以取里面变量的值)
    AcWing—SpringBoot【学习笔记】(整合)_第25张图片




2、游戏界面

2.1、大致设计

AcWing—SpringBoot【学习笔记】(整合)_第26张图片
AcWing—SpringBoot【学习笔记】(整合)_第27张图片

2.2、基类的创建

  • 为了让游戏动起来 =》 要搞一个可以不断刷新页面的功能 =》 做一个类来完成每秒刷新页面60次
  • 首先在src/assets/中创建好对应的文件夹scripts和images,创建好对应的基类AcGameObjects.js,这里刷新的原理是,调用requestAnimationFrame(step)在下一帧渲染浏览器之前,就执行step一次,然后这个step函数中再去调用一下requestAnimationFrame(step)就完成了每一帧都渲染一次,这里y总讲的实现的方法我也没太理解了 ,大致写游戏帧刷新的基本都是这样的,先记住再理解吧!
    AcWing—SpringBoot【学习笔记】(整合)_第28张图片
const AC_GAME_OBJECTS = [];

export class AcGameObject {
    constructor(){
        AC_GAME_OBJECTS.push(this);
        // 当移动起来 需要一个 速度的概念 1s中移动几个像素 然后这里有推到使用时间
        // 这一帧与上一帧 执行 之间的时间间隔
        this.timedelta = 0;
        //是否执行过
        this.has_called_start = false;
    }
    start(){   //  只执行一次

    }
    update() { // 每一帧执行一次,除了第一帧之外

    }
    on_destory() {  // 删除之前执行

    }   
    destory() {
        this.on_destory();

        //用in的话遍历的是下标
        for (let i in AC_GAME_OBJECTS){
            const obj = AC_GAME_OBJECTS[i];
            if(obj === this){
                AC_GAME_OBJECTS.splice(i);
                break;
            }
        }
    }
}
let last_timestamp; //上一次执行的时刻
const step = timestamp => {
    // 用of遍历的是值
    for(let obj of AC_GAME_OBJECTS){
        if(!obj.has_called_start){
            obj.has_called_start = true;
            obj.start();
        }else{
            obj.timedelta = timestamp - last_timestamp;
            obj.update();
        }
    }
    last_timestamp = timestamp;
    requestAnimationFrame(step);
}
requestAnimationFrame(step);

2.3、地图类

  • 大致样子
    AcWing—SpringBoot【学习笔记】(整合)_第29张图片
import { AcGameObject } from "./AcGameObject";
export class GameMap extends AcGameObject{
    // ctx画布  parent画布的父元素
    constructor(ctx, parent){
        super();
        this.ctx = ctx;
        this.parent = parent;
        //绝对距离
        this.L = 0;
    }
    start(){
    }
    update(){
        this.render();
    }
    render(){
    }
}

2.4、对战-游戏区域

  • 这里真的我觉的就像java里面那个布局管理一样,先搞一个大的游戏的区域,然后在这块区域中再加一些别的东西,一个盘子加一个盘子
  • 先创建一个游戏区域,这里就是下图里面的一个大的盘子
    AcWing—SpringBoot【学习笔记】(整合)_第30张图片
  • y总这里展示拉这个浏览器,这个区域会进行一些变化,我这里觉的y总是想把那个蛇的游玩的界面搞到这个蓝色的区域中,当这个浏览器发生变化的时候,游玩的界面也会跟着变形,下图为增加游玩的的界面
    AcWing—SpringBoot【学习笔记】(整合)_第31张图片
  • 然后该在这个主界面中加载一些游戏的对象
    AcWing—SpringBoot【学习笔记】(整合)_第32张图片
  • 然后我这里遇到了一个巨大的错误,花了我好长时间!!!!!!这里没有加东西,components啊啊啊啊!!!

AcWing—SpringBoot【学习笔记】(整合)_第33张图片

  • 然后就解决当浏览器变化的时候,中间的游玩的区域动态变化的问题这,这里我才看懂那个ctx、和parent画布是啥意思,这个js被GameMap.vue中使用,并创建对象,那个ctx就是GameMap.vue中的标签里面的一样大,这里y总设置的GameMap.vue的是空的,这里的ctx初始就是空的,这个parent就是外面那个大的蓝色的区域,然后进行一个y总的算法计算,求出游玩的区域总能在parent中的正正好好出现的位置
    AcWing—SpringBoot【学习笔记】(整合)_第34张图片
    AcWing—SpringBoot【学习笔记】(整合)_第35张图片
  • 最后实现出来的效果就是下图 ,在不同的浏览器页面的大小下可以自动的变化
    AcWing—SpringBoot【学习笔记】(整合)_第36张图片
    AcWing—SpringBoot【学习笔记】(整合)_第37张图片
  • 然后在给中间的游玩区域加上小格子,可以枚举这13*13的格子,然后通过奇偶数来确定格子的颜色,然后将他们画出来
    AcWing—SpringBoot【学习笔记】(整合)_第38张图片
 render(){
        // color_even是偶数格子   color_odd是奇数格子
        const color_even = "#AAD751", color_odd = "#A2D149";
        // 枚举格子填充颜色
        for (let r = 0; r < this.rows; r++){
            for (let c = 0; c < this.cols; c++){
                if((r + c) % 2 == 0){
                    //选颜色
                    this.ctx.fillStyle = color_even;
                }else{
                    this.ctx.fillStyle = color_odd;
                }
                //画方块
                this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L);
            }
        }
    }

  • 给游玩界面加上四周的边界(障碍物),有限定的范围才是对的,这里又要新建一个组件Wall.js
    AcWing—SpringBoot【学习笔记】(整合)_第39张图片
  • 然后在GameMap.vue中去使用,将四周的格子通过true和false来绘画
    AcWing—SpringBoot【学习笔记】(整合)_第40张图片
    AcWing—SpringBoot【学习笔记】(整合)_第41张图片
  • 然后在游玩的界面中间创建随机的障碍物,每次刷新界面都会生成新的障碍物
    AcWing—SpringBoot【学习笔记】(整合)_第42张图片
  • 啊!这里用到算法了,没学过算法的很难受!这里还有一个问题,就是这个游戏左下角到右上角必须要有一条通路,这里y总是用了一个算法找到的这条路(算法基础课 必学!!!)
    AcWing—SpringBoot【学习笔记】(整合)_第43张图片
  • 这里要解决一个地图上遗留的问题,关于两条蛇走向同一个格子的时候,两条蛇会莫名奇妙的平局,对游戏ai策略不是很好,当两条蛇能在同一时间走到同一个格子的时候,会对优势者产生不利的影响,这里要调整一个地图,使得两条蛇不会在同一时刻进入到同一个格子里面,避免这种尴尬局面发生,这里需要修改地图的长宽,一条蛇的起始坐标是(11,1)、(1,11)这里要修改一个地图的列数,使得他们的起始坐标变成(11,1)、(1,12)这样蛇的头结点一直就是奇偶对立情况了,就不会进到同一个格子里面了。
    AcWing—SpringBoot【学习笔记】(整合)_第44张图片
  • 这里又有一个问题,就是变成了12*13的格子了不满足轴对称了,所以要修改策略生成一个长方形的中心对称的的地图
    AcWing—SpringBoot【学习笔记】(整合)_第45张图片
    AcWing—SpringBoot【学习笔记】(整合)_第46张图片

3、蛇的设计

3.1、蛇的步长变化

AcWing—SpringBoot【学习笔记】(整合)_第47张图片

3.2、蛇的雏形

  • 创建一个格子类,把要画的蛇的身体(圆形)在格子中的位置写出来
    AcWing—SpringBoot【学习笔记】(整合)_第48张图片
  • 定义一个Shake.js来继承格子那个类,里面有画蛇的方法,只要有new这个类的话就会在指定的地方画出一个圆来
    AcWing—SpringBoot【学习笔记】(整合)_第49张图片
  • 在GameMap.vue创建两条蛇看看
    AcWing—SpringBoot【学习笔记】(整合)_第50张图片
    AcWing—SpringBoot【学习笔记】(整合)_第51张图片
  • 让蛇简单的动起来
    AcWing—SpringBoot【学习笔记】(整合)_第52张图片
  • 哦,我这里才意识到timedelta是怎么来的了,查了一下知道
    AcWing—SpringBoot【学习笔记】(整合)_第53张图片
    AcWing—SpringBoot【学习笔记】(整合)_第54张图片
  • 然后蛇头就会动起来了,这里是向右猛走,穿墙走。。。
    AcWing—SpringBoot【学习笔记】(整合)_第55张图片
  • 完成蛇的移动问题,先考虑这个蛇移动的策略,蛇走不走,往哪走,怎么走,在蛇类中添加以下的属性和方法,通过获取蛇要走的方向,通过方向和偏移量来计算下一个要移动的格子的位置,然后创建新的格子
    AcWing—SpringBoot【学习笔记】(整合)_第56张图片
  • 在GameMap.vue中判断蛇是否能走,能走的话就去调用shake中的往下一步走的指令就可以了。
    AcWing—SpringBoot【学习笔记】(整合)_第57张图片
  • 这里需要direction才可以去判断是否可以走下一步了,所以要使得这个游戏界面可以获得外部输入的数据来给direction赋值
    AcWing—SpringBoot【学习笔记】(整合)_第58张图片
    在这里插入图片描述
    AcWing—SpringBoot【学习笔记】(整合)_第59张图片
    AcWing—SpringBoot【学习笔记】(整合)_第60张图片
  • 开始的时候y总提供的蛇的移动的策略是新建一个头,然后去掉尾巴(增头去尾),我认为啊目前是猜测,就是蛇的长度肯定的固定的,然后当蛇先出来的时候,没有到蛇的最长的长度的时候,每次移动一下就创建一个新的头,当到达了蛇的长度的时候,增头去尾就展示出来了,所以这里y总目前实现了一部分的代码
    AcWing—SpringBoot【学习笔记】(整合)_第61张图片
  • 这里要实现的是蛇移动过程的代码
    AcWing—SpringBoot【学习笔记】(整合)_第62张图片
    AcWing—SpringBoot【学习笔记】(整合)_第63张图片
  • 这里我有个bug刚才找到了
    在这里插入图片描述
    AcWing—SpringBoot【学习笔记】(整合)_第64张图片
  • 这样的话蛇就可以通过键盘来动起来了
    AcWing—SpringBoot【学习笔记】(整合)_第65张图片
  • 这里要解决我之前猜测的那个了,蛇的长度是不固定的,是10步之内每次都要变长,10步之外走三步就变长一次,然后当蛇不变长的时候,就是蛇尾去掉的时候,也就是实现了增头去尾的效果
    AcWing—SpringBoot【学习笔记】(整合)_第66张图片
  • 又找到了bug
    AcWing—SpringBoot【学习笔记】(整合)_第67张图片
    AcWing—SpringBoot【学习笔记】(整合)_第68张图片
  • 现在的蛇看起来很不好看,给蛇身体中间的圆形,每两个圆形就加一个矩形,就好了
    AcWing—SpringBoot【学习笔记】(整合)_第69张图片
    AcWing—SpringBoot【学习笔记】(整合)_第70张图片
    AcWing—SpringBoot【学习笔记】(整合)_第71张图片
  • 写die逻辑,就是撞到了蛇或者是墙的话就直接死了,瞬间变白
    AcWing—SpringBoot【学习笔记】(整合)_第72张图片
    AcWing—SpringBoot【学习笔记】(整合)_第73张图片
    AcWing—SpringBoot【学习笔记】(整合)_第74张图片
  • 实现一下眼睛,这里又用到了偏移量
    AcWing—SpringBoot【学习笔记】(整合)_第75张图片
    AcWing—SpringBoot【学习笔记】(整合)_第76张图片
    AcWing—SpringBoot【学习笔记】(整合)_第77张图片
    AcWing—SpringBoot【学习笔记】(整合)_第78张图片

三、配置Mysql与注册登录模块

1、配置mysql

1.1、安装myql

  • 如果之前下载过mysql,先要将mysql卸载干净,具体操作可以在网上搜教程,我看到的是这个
  • 安装mysql可参考大佬教程
  • 我在安装的过程中是遇到了在一个init databases的地方卡住了,然后我又重新卸载了几次最后是在这个地方勾上了就解决了,不知道怎么搞的反正结果是好的
    AcWing—SpringBoot【学习笔记】(整合)_第79张图片

1.2、idea中使用mysql

  • 打开idea在右侧边栏有一个database
    AcWing—SpringBoot【学习笔记】(整合)_第80张图片
    AcWing—SpringBoot【学习笔记】(整合)_第81张图片

  • 然后点开输入用户名、密码,要连接的数据库的名字,最后测试连接一下(如果是第一次连接的话,按照指示idea自动下载一些驱动)
    AcWing—SpringBoot【学习笔记】(整合)_第82张图片
    在这里插入图片描述

2、配置SpringBoot

2.1、配置pom.xml依赖

  • 在这里去搜索依赖,这里y总先让配置这几个依赖,放在pom.xml中的标签中
依赖名 作用
Spring Boot Starter JDBC JDBC依赖
Project Lombok 写pojo的注解
MySQL Connector/J MySQL官方JDBC驱动程序
mybatis-plus-boot-starter MyBatis的增强工具
mybatis-plus-generator 代码生成器

AcWing—SpringBoot【学习笔记】(整合)_第83张图片

2.2、访问数据库配置

  • 将y总给的代码粘贴一下即可
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/kob?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

AcWing—SpringBoot【学习笔记】(整合)_第84张图片

2.3、SpringBoot层的概念

功能
pojo层 将数据库中的表对应成Java中的Class
mapper层(也叫Dao层) 将pojo层的class中的操作,映射成sql语句
service层 写具体的业务逻辑,组合使用mapper中的操作
controller层 负责请求转发,接受页面过来的参数,传给Service处理,接到返回值,再传给页面

2.4、SpringBoot简单调试-查询-插入-删除

  • 第一次见,就做点笔记记录一下吧
    AcWing—SpringBoot【学习笔记】(整合)_第85张图片
    在这里插入图片描述
    AcWing—SpringBoot【学习笔记】(整合)_第86张图片
    在这里插入图片描述

  • 注:上面这个图片里面这些操作理论上应该写到server中而不是写到controller层中,这里是因为演示一下,还有其它的api请查看这个AcWing—SpringBoot【学习笔记】(整合)_第87张图片
    AcWing—SpringBoot【学习笔记】(整合)_第88张图片

2.5、SpringBoot安全访问配置

  • 还是先导入依赖
依赖 作用
spring-boot-starter-security Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。
  • 配置好依赖后,再次启动,当你访问任何一个url的时候就会出现这个(username是user,password是在springboot启动状态下面动态生成的)
    在这里插入图片描述
    AcWing—SpringBoot【学习笔记】(整合)_第89张图片

  • 修改SpringBoot安全访问配置,首先要创建一个UserDetailsServiceImpl类来继承UserDetailsService来接入数据库信息,然后再实现一个utils的工具类UserDetailsImpl来实现UserDetails。最后的逻辑就是在那个安全验证中输入你的账号和密码,如果在数据库中找到了username就将这个条字段作为通关令牌存起来,当退出的时候再删除

  • 注:这里如果没有加密的话要使用的话在密码那个字段的属性中加一个{noop}

AcWing—SpringBoot【学习笔记】(整合)_第90张图片
AcWing—SpringBoot【学习笔记】(整合)_第91张图片

AcWing—SpringBoot【学习笔记】(整合)_第92张图片

2.6、SpringBoot密码加密

  • 首先是创建一个SecurityConfig类用来实现用户密码的加密存储
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

AcWing—SpringBoot【学习笔记】(整合)_第93张图片

  • 然后再将你数据库中的密码加密重新重置一下
    AcWing—SpringBoot【学习笔记】(整合)_第94张图片
  • 然后修改一下UserController中的addUser方法
    AcWing—SpringBoot【学习笔记】(整合)_第95张图片
  • 最后数据库中的数据
    AcWing—SpringBoot【学习笔记】(整合)_第96张图片
  • 然后就可以通过你设置的密码(以一种加密方式来通过安全验证了)!!!
  • y总补充验证模式(传统模式)
    AcWing—SpringBoot【学习笔记】(整合)_第97张图片

3、Jwt验证方式(流行版本)

3.1、简单讲述

  • 容易实现跨域,不需要在多个服务端存储
  • 第一步用户登录成功后,会给用户一个jwt-token,会将用户id或者用户信息组成一个字符串
  • 第二步通过一些固定的机密算法把jwt-token映射成一组新的加密后的字符串
  • 第三步将userId和加密后的字符串返回给用户形成一个jwt-token
    AcWing—SpringBoot【学习笔记】(整合)_第98张图片
    • 一般会给用户传递两个token,一个是access-token,一个是refresh-token(有效期),每次向服务端发送请求的时候,发送access-token,因为有时候请求时GET或者是POST,GET是明文的,所以要用这个,如果access-token过期的话,通过一个POST请求发送refresh-token去刷新access-token
      AcWing—SpringBoot【学习笔记】(整合)_第99张图片
  • 注:如果有人想通过修改userId然后获取其他人的权限的时候,这种方法是不现实的,因为他只知道userId而不知道秘钥,他只拥有userId加上加密后的字符串,秘钥通过反推得到的话,几乎是不可能的

3.2、配置Jwt

  • 首先要导入依赖
依赖名 作用
jjwt-api
jjwt-impl
jjwt-jackson
  • 然后增加三个类
    • 首先是实现utils.JwtUtil类,为jwt工具类,用来创建、解析jwt token
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

@Component
public class JwtUtil {
    public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;  // 有效期14天
    public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";

    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }

        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)
                .setSubject(subject)
                .setIssuer("sg")
                .setIssuedAt(now)
                .signWith(signatureAlgorithm, secretKey)
                .setExpiration(expDate);
    }

    public static SecretKey generalKey() {
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }

    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(jwt)
                .getBody();
    }
}

在这里插入图片描述

  • 实现config.filter.JwtAuthenticationTokenFilter类,用来验证jwt token,如果验证成功,则将User信息注入上下文中
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private UserMapper userMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");

        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        token = token.substring(7);

        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        User user = userMapper.selectById(Integer.parseInt(userid));

        if (user == null) {
            throw new RuntimeException("用户名未登录");
        }

        UserDetailsImpl loginUser = new UserDetailsImpl(user);
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, null);

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request, response);
    }
}

AcWing—SpringBoot【学习笔记】(整合)_第100张图片

  • 配置config.SecurityConfig类,放行登录、注册等接口
import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/user/account/token/", "/user/account/register/").permitAll()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated();

        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

AcWing—SpringBoot【学习笔记】(整合)_第101张图片

  • 然后将数据库中的id属性变为自增,在pojo中加上@TableId(type = IdType.AUTO),可以使用以下的语句重新创建表
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) ,
  `password` varchar(100) ,
  `photo` varchar(1000) ,
  PRIMARY KEY (`id`) 
)

AcWing—SpringBoot【学习笔记】(整合)_第102张图片

3.3、编写Login的API

  • 首先在service层中user/account/创建一个LoginService接口,定义一个方法LoginServiceImpl
    AcWing—SpringBoot【学习笔记】(整合)_第103张图片
  • 然后在service层中impl/user/account/创建一个LoginServiceImpl方法,实现LoginService接口,然后编写方法
package com.kob.backend.service.impl.user.account;

import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.service.user.account.LoginService;
import com.kob.backend.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    // 认证管理器
    private AuthenticationManager authenticationManager;
    @Override
    public Map<String, String> getToken(String username, String password) {
        // 将输入进来的username和password封装成一个密码加密过后的对象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(username, password);
        // 该方法接收一个认证令牌对象,也就是认证请求作为参数,如果其中的信息匹配到目标账号,
        // 则该方法返回同一个认证令牌对象,不过其中被认证过程填充了更多的账号信息,比如授权和用户详情等。
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);// 如果登录失败会自动报异常
        UserDetailsImpl loginUser = (UserDetailsImpl)authenticate.getPrincipal();
        User user = loginUser.getUser();
        String jwt = JwtUtil.createJWT(user.getId().toString());

        Map<String, String> map = new HashMap<>();
        map.put("error_message", "success");
        map.put("token", jwt);

        return map;
    }
}

AcWing—SpringBoot【学习笔记】(整合)_第104张图片

  • 然后在controller层/user/account/中创建一个LoginController
    AcWing—SpringBoot【学习笔记】(整合)_第105张图片
  • 因为这里有密码所以用的是post请求从地址栏中访问不到,所以用前端的vue去测试访问一下
    AcWing—SpringBoot【学习笔记】(整合)_第106张图片
    AcWing—SpringBoot【学习笔记】(整合)_第107张图片

3.4、编写Info的API

  • 首先在service层中user/account/创建一个InfoService接口,定义一个方法InfoServiceImpl
    AcWing—SpringBoot【学习笔记】(整合)_第108张图片
    AcWing—SpringBoot【学习笔记】(整合)_第109张图片
  • 然后在controller层user/account/中创建一个InfoController
    AcWing—SpringBoot【学习笔记】(整合)_第110张图片
  • 在前端测试一下
    在这里插入图片描述
    AcWing—SpringBoot【学习笔记】(整合)_第111张图片

3.5、编写register的API

  • 首先在service层中user/account/创建一个RegisterService接口,定义一个方法RegisterServiceImpl

AcWing—SpringBoot【学习笔记】(整合)_第112张图片

package com.kob.backend.service.impl.user.account;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.user.account.RegisterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class RegisterServiceImpl implements RegisterService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public Map<String, String> register(String username, String password, String confirmedPassword) {
        Map<String, String> map = new HashMap<>();
        // 判断有没有username这个参数
        if(username == null) {
            map.put("error_message", "用户名不能为空");
            return map;
        }
        // 判断有没有password和confirmedPassord这个参数
        if(password == null || confirmedPassword == null) {
            map.put("error_message", "密码不能为空");
            return map;
        }
        // 删除前后的空格
        username = username.trim();
        // 判断username是否为空
        if(username.length() == 0){
            map.put("error_message", "用户名不能为空");
            return map;
        }

        // 判断password是否为空
        if(password.length() == 0 || confirmedPassword.length() == 0){
            map.put("error_message", "密码不能为空");
            return map;
        }

        // 判断用户名的长度
        if(username.length() > 100){
            map.put("error_message", "用户名长度不能大于100");
            return map;
        }

        // 判断密码的长度
        if(password.length() > 100 || confirmedPassword.length() > 100){
            map.put("error_message", "密码长度不能大于100");
            return map;
        }

        // 判断两次密码是否一致
        if(!password.equals(confirmedPassword)){
            map.put("error_message", "两次输入的密码不一致");
            return map;
        }

        // 查询数据库中是否有这个用户名
        // 定义一个条件查询器
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 定义条件
        queryWrapper.eq("username", username);
        // 查询
        List<User> users = userMapper.selectList(queryWrapper);
        // 判断
        if(!users.isEmpty()){
            map.put("error_message", "用户名已经存在");
            return map;
        }

        // 加密
        String encodedPassword = passwordEncoder.encode(password);
        String photo = "https://cdn.acwing.com/media/user/profile/photo/239860_lg_086619de6f.jpg";
        User user = new User(null, username, password, photo);
        int insert = userMapper.insert(user);
        map.put("error_message", "注册成功");
        return map;
    }
}

AcWing—SpringBoot【学习笔记】(整合)_第113张图片

  • 然后在controller层user/account/中创建一个RegisterController
    AcWing—SpringBoot【学习笔记】(整合)_第114张图片
  • 最后在前端测试一下
    AcWing—SpringBoot【学习笔记】(整合)_第115张图片
    AcWing—SpringBoot【学习笔记】(整合)_第116张图片
    AcWing—SpringBoot【学习笔记】(整合)_第117张图片

4、前端登录页面和注册页面

4.1、登录页面

  • 首先创建一个vue页面,然后从bootstramp抄点登录框的样式就可以了
    AcWing—SpringBoot【学习笔记】(整合)_第118张图片

  • 然后在router配置路由
    AcWing—SpringBoot【学习笔记】(整合)_第119张图片
    AcWing—SpringBoot【学习笔记】(整合)_第120张图片

  • 然后访问查看一下
    AcWing—SpringBoot【学习笔记】(整合)_第121张图片

  • 就像是cookie一样当你登录成功以后,前端就应该去持有一个全局的用户信息,在vue中需要这样设置需要使用vuex,也就是store,单独写一个user.js然后导入到index.js模块化
    AcWing—SpringBoot【学习笔记】(整合)_第122张图片
    AcWing—SpringBoot【学习笔记】(整合)_第123张图片

  • 然后再其他页面去调用他
    AcWing—SpringBoot【学习笔记】(整合)_第124张图片

  • 然后如果想要显示出来右上面那个东西的话
    AcWing—SpringBoot【学习笔记】(整合)_第125张图片

  • 最后就变成了这样
    AcWing—SpringBoot【学习笔记】(整合)_第126张图片
    AcWing—SpringBoot【学习笔记】(整合)_第127张图片

4.2、前端页面授权

  • 这里要使用一个router的api来实现前端页面的授权
    AcWing—SpringBoot【学习笔记】(整合)_第128张图片
    AcWing—SpringBoot【学习笔记】(整合)_第129张图片

4.3、注册页面

  • 页面大致和登录页面的差不多改一改就行了





AcWing—SpringBoot【学习笔记】(整合)_第130张图片
AcWing—SpringBoot【学习笔记】(整合)_第131张图片
AcWing—SpringBoot【学习笔记】(整合)_第132张图片

4.4、登录页面持久化

  • 原理就是将token放到localStorge中去,登录成功的时候放进去,退出的时清除掉,刷新的时候在登录页面做一层判断,如果能从localStorge中能获取到token,然后用token去调用一下getinfo请求,如果可以成功的话,那么就会自动跳转到主页咯
    AcWing—SpringBoot【学习笔记】(整合)_第133张图片
    AcWing—SpringBoot【学习笔记】(整合)_第134张图片
    AcWing—SpringBoot【学习笔记】(整合)_第135张图片

  • 然后优化一个当你localStorage中有正确的token的时候,你刷新页面的时候,后面会闪一下那个登录页面,导航栏会闪一下那个登录注册,很不好看,所以我们要优化一下

  • 首先在全局变量这里加一个true,如果正在拉取的状态下不能显示登录界面和登录、注册按钮
    AcWing—SpringBoot【学习笔记】(整合)_第136张图片

  • 然后登录页面修改
    AcWing—SpringBoot【学习笔记】(整合)_第137张图片
    AcWing—SpringBoot【学习笔记】(整合)_第138张图片

  • 修改NavBar.vue中的代码
    AcWing—SpringBoot【学习笔记】(整合)_第139张图片- 最后点击刷新也不会后面闪一下登录界面和登录、注册按钮了

四、创建个人中心页面

1、后端编写API

1.1、建表、pojo、mapper、service接口

  • 我是习惯用命令去建表,这里我们要实现三个api所以,要新建一个bot表来存放数据,可以用以下的sql语句来建表
CREATE TABLE `kob`.`bot`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `title` varchar(100) NULL,
  `description` varchar(300) NULL,
  `content` varchar(10000) NULL,
  `rating` int NULL DEFAULT 1500,
  `createtime` datetime NULL,
  `modifytime` datetime NULL,
  PRIMARY KEY (`id`)
);
  • 编写对应的pojo类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Bot {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private Integer userId;
    private String title;
    private String description;
    private String content;
    private Integer rating;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createtime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date modifytime;
}

AcWing—SpringBoot【学习笔记】(整合)_第140张图片

  • 编写mapper层
    AcWing—SpringBoot【学习笔记】(整合)_第141张图片
  • 编写service接口
public interface AddService {
    public Map<String, String> add(Map<String, String> data);
}
public interface GetListService {
    public List<Bot> getlist();
}
public interface RemoveService {
    public Map<String, String> remove(Map<String, String> data);
}
public interface UpdateService {
    public Map<String, String> update(Map<String, String> map);
}

AcWing—SpringBoot【学习笔记】(整合)_第142张图片

1.2、编写AddServiceImpl和AddController

  • 编写AddServiceImpl
@Service
public class AddServiceImpl implements AddService {
    @Autowired
    private BotMapper botMapper;
    @Override
    public Map<String, String> add(Map<String, String> data) {
        UsernamePasswordAuthenticationToken authenticationToken =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        UserDetailsImpl loginUser = (UserDetailsImpl)authenticationToken.getPrincipal();
        User user = loginUser.getUser();

        String title = data.get("title");
        String description = data.get("dsecription");
        String content = data.get("content");

        Map<String, String> map = new HashMap<>();

        if(title == null || title.length() == 0){
            map.put("error_message", "标题不能为空");
            return map;
        }

        if(title.length() > 100) {
            map.put("error_message", "标题长度不能大于100");
            return map;
        }

        if(description == null || description.length() == 0){
            description = "这个用户很懒,什么也没写";
        }

        if(description.length() > 300){
            map.put("error_message", "Bot描述长度不能大于300");
            return map;
        }

        if(content == null || content.length() == 0){
            map.put("error_message", "代码不能为空");
            return map;
        }
        if(content.length() > 10000){
            map.put("error_message", "代码长度不能大于100000");
            return map;
        }
        Date date = new Date();
        Bot bot = new Bot(null, user.getId(), title, description, content, 1500, date, date);

        botMapper.insert(bot);
        map.put("error_message", "success");
        return map;
    }
}

AcWing—SpringBoot【学习笔记】(整合)_第143张图片
AcWing—SpringBoot【学习笔记】(整合)_第144张图片

  • 编写AddController
    在这里插入图片描述
  • 然后在前端请求测试一下
    在这里插入图片描述
  • 可以返回succes然后在数据库中能出现加的内容即完成

1.3、编写RemoveServiceImpl和RemoveController

@Service
public class RemoveServiceImpl implements RemoveService {
    @Autowired
    private BotMapper botMapper;
    @Override
    public Map<String, String> remove(Map<String, String> data) {
        int bot_id = Integer.parseInt(data.get("bot_id"));
        System.out.println(bot_id);
        UsernamePasswordAuthenticationToken authenticationToken =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
        User user = loginUser.getUser();

        Bot bot = botMapper.selectById(bot_id);
        Map<String, String> map = new HashMap<>();
        if(bot == null){
            map.put("error_message", "Bot不存在或已经删除");
            return map;
        }
        if(!bot.getUserId().equals(user.getId())){
            map.put("error_message", "没有权限删除该Bot");
            return map;
        }
        botMapper.deleteById(bot_id);
        map.put("error_message", "success");
        return map;
    }

}

AcWing—SpringBoot【学习笔记】(整合)_第145张图片

AcWing—SpringBoot【学习笔记】(整合)_第146张图片

  • 前端测试一下
    AcWing—SpringBoot【学习笔记】(整合)_第147张图片

1.4、编写UpdateServiceImpl和UpdateController

@Service
public class UpdateServiceImpl implements UpdateService {
    @Autowired
    private BotMapper botMapper;
    @Override
    public Map<String, String> update(Map<String, String> data) {
        UsernamePasswordAuthenticationToken authenticationToken =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        UserDetailsImpl loginUser= (UserDetailsImpl) authenticationToken.getPrincipal();
        User user = loginUser.getUser();

        int bot_id = Integer.parseInt(data.get("bot_id"));

        String title = data.get("title");
        String description = data.get("description");
        String content = data.get("content");

        Map<String, String> map = new HashMap<>();

        if(title == null || title.length() == 0){
            map.put("error_message", "标题不能为空");
            return map;
        }

        if(title.length() > 100) {
            map.put("error_message", "标题长度不能大于100");
            return map;
        }

        if(description == null || description.length() == 0){
            description = "这个用户很懒,什么也没写";
        }

        if(description.length() > 300){
            map.put("error_message", "Bot描述长度不能大于300");
            return map;
        }

        if(content == null || content.length() == 0){
            map.put("error_message", "代码不能为空");
            return map;
        }
        if(content.length() > 10000){
            map.put("error_message", "代码长度不能大于100000");
            return map;
        }

        Bot bot = botMapper.selectById(bot_id);

        if(bot == null){
            map.put("error_message", "Bot不存在或已被删除");
            return map;
        }

        if(!bot.getUserId().equals(user.getId())){
            map.put("error_message", "没有权限修改该Bot");
            return map;
        }

        Bot new_bot = new Bot(bot.getId(), user.getId(), title, description, content, bot.getRating(), bot.getCreatetime(), new Date());
        botMapper.updateById(new_bot);
        map.put("error_message", "success");

        return map;
    }
}

AcWing—SpringBoot【学习笔记】(整合)_第148张图片

AcWing—SpringBoot【学习笔记】(整合)_第149张图片

  • 然后在前端测试一下
    AcWing—SpringBoot【学习笔记】(整合)_第150张图片

1.5、编写GetListServiceImpl和GetListController

AcWing—SpringBoot【学习笔记】(整合)_第151张图片
在这里插入图片描述

  • 然后去前端测试一下
    AcWing—SpringBoot【学习笔记】(整合)_第152张图片

2、前端个人中心页面实现

2.1、创建大致的页面






AcWing—SpringBoot【学习笔记】(整合)_第153张图片
AcWing—SpringBoot【学习笔记】(整合)_第154张图片
AcWing—SpringBoot【学习笔记】(整合)_第155张图片

2.1、编写添加按钮弹出框

AcWing—SpringBoot【学习笔记】(整合)_第156张图片
AcWing—SpringBoot【学习笔记】(整合)_第157张图片

2.2、添加按钮逻辑

AcWing—SpringBoot【学习笔记】(整合)_第158张图片
AcWing—SpringBoot【学习笔记】(整合)_第159张图片

AcWing—SpringBoot【学习笔记】(整合)_第160张图片

2.3、删除按钮逻辑

AcWing—SpringBoot【学习笔记】(整合)_第161张图片
AcWing—SpringBoot【学习笔记】(整合)_第162张图片

2.4、修改按钮逻辑

AcWing—SpringBoot【学习笔记】(整合)_第163张图片
AcWing—SpringBoot【学习笔记】(整合)_第164张图片

2.5、将代码区域换成编译器

  • 首先要装一下依赖vue3-ace-editor
  • 先把下面这段放到js里面
import { VAceEditor } from 'vue3-ace-editor';
import ace from 'ace-builds';

AcWing—SpringBoot【学习笔记】(整合)_第165张图片

  • 然后把下面这段放到setup里面
import { VAceEditor } from 'vue3-ace-editor';
import ace from 'ace-builds';

AcWing—SpringBoot【学习笔记】(整合)_第166张图片

  • 组件导入
    AcWing—SpringBoot【学习笔记】(整合)_第167张图片
    AcWing—SpringBoot【学习笔记】(整合)_第168张图片

  • 最后把这段替换掉代码区域

<VAceEditor
    v-model:value="botadd.content"
    @init="editorInit"
    lang="c_cpp"
    theme="textmate"
    style="height: 300px" />

AcWing—SpringBoot【学习笔记】(整合)_第169张图片
AcWing—SpringBoot【学习笔记】(整合)_第170张图片
AcWing—SpringBoot【学习笔记】(整合)_第171张图片

2.6、删除点击确定






AcWing—SpringBoot【学习笔记】(整合)_第172张图片
AcWing—SpringBoot【学习笔记】(整合)_第173张图片

五、实现微服务

1、知识点补充

1.1、http协议的实现

  • 超文本传输协议,用于从 www 服务器传输超文本到本地浏览器的传输协议。http 是基于 tcp 协议的一个应用层的协议,由请求和相应构成;是一个无状态的协议。
  • 工作过程

1、客户端和服务端建立连接,http开始工作;
2、建立连接后客户端发送请求给服务器;
3、服务器收到请求后,给予相应的响应信息;
4、客户端接收服务器返回的数据并在浏览器上展示,然后客户端和服务器连接断开。

  • 在这个过程中客户端和服务器之间的连接和断开是怎么实现的呢?
  • 三次握手建立连接

第一次:建立连接,客户端发送 SYN 包(SYN=i)到服务器,并进入 SYN_SEND 状态,等待服务器确认;
第二次:服务器接收 SYN 包,确认客户的 SYN ,同时也发送一个 SYN 包(SYN=k)+ ACK 包(ACK = i+1)给客户端,服务器进入 SYN_RECV 状态;
第三次:客户端接收到服务器的 SYN+ACK 包之后,同时向服务器发送确认包 ACK 包(ACK = k+1),然后客户端和服务器进入ESTABLISHED 状态。

完成三次握手之后,客户端和服务器开始传输数据。

  • 四次挥手断开连接

第一次:客户端发送一个 FIN(i) ,用来关闭客户端和服务器的数据传输,客户端进入 FIN_WAIT_1 状态;
第二次:服务器接收 FIN ,发送一个 ACK (i+1)给客户端,服务器进入 CLISE_WAIT 状态;
第三次:服务器发送一个 FIN (j) 给客户端用来关闭服务器到客户端的数据传输,服务器进入 LAST_ACK 状态;
第四次:客户端接收到 FIN 后,客户端进入 TIME_WAIT 状态,然后发送 ACK (j+1) 给服务器,校验完成之后服务器进入 CLOSED 状态。

1.2、websocket协议的实现

  • websocket 是 H5 的提出的在单个 TCP 协议上进行全双工通讯的协议。它允许服务器主动向客户端推送数据,客户端和服务器只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。

  • websocket 是基于 http 协议的。借助 http 协议来完成握手。

  • 工作过程:

1、客户端发送 http 请求,经过三次握手,建立 TCP连接,在 http 请求里存放 websocket 支持的版本号等信息;
2、服务器接收请求,同样以 http 协议回应;
3、连接成功,客户端与服务器建立持久性的连接。

连接之后客户端和服务器之间就可以随时通讯,直到其中一方关闭连接。

1.3、两者的区别

  • 相同点

都是 TCP 协议;
都使用 Request/Response 模型进行连接的建立;
websocket 是基于 http 的,他们的兼容性都很好;
在连接的建立过程中对错误的处理方式相同;
都可以在网络中传输数据。

  • 不同点

websocket 是持久连接,http 是短连接;
websocket 的协议是以 ws/wss 开头,http 对应的是 http/https;
websocket 是有状态的,http 是无状态的;
websocket 连接之后服务器和客户端可以双向发送数据,http 只能是客户端发起一次请求之后,服务器才能返回数据;
websocket 是可以跨域的;
websocket 连接建立之后,数据的传输使用帧来传递,不再需要Request消息。

可参考:websocket

AcWing—SpringBoot【学习笔记】(整合)_第174张图片

2、环境的配置和配置类

依赖名称 作用
spring-boot-starter-websocket 实现websocket的依赖
fastjson 将java对象转化为json,将json转化为java对象
  • 添加config.WebSocketConfig配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {

        return new ServerEndpointExporter();
    }
}

  • 添加consumer.WebSocketServer类
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

@Component
@ServerEndpoint("/websocket/{token}")  // 注意不要以'/'结尾
public class WebSocketServer {
    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {
        // 建立连接
    }

    @OnClose
    public void onClose() {
        // 关闭链接
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        // 从Client接收消息
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }
}
  • 在config.SecurityConfig添加这个
@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/websocket/**");
}

3、代码编写

3.1、修改后端

  • 先修改成以下这样子的
    参考方法注入
@Component
@ServerEndpoint("/websocket/{token}")  // 注意不要以'/'结尾
public class WebSocketServer {
    // 静态变量存储所有链接
    private static ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();
    // 将session存在每一个user中,user持有session
    private User user;
    // websocket中的session  每个链接用session来维护的
    private Session session = null;
    // 下面要查询userId对应的数据,所以要注入一个usermapper,
    private static UserMapper userMapper;
    // 这里要注入的方式有些不同,注入方式好像是和WebSocket不是一个单例模式有关系
    @Autowired
    public void setUserMapper(UserMapper userMapper){
        WebSocketServer.userMapper = userMapper;
    }

    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {
        // 建立连接
        this.session = session;
        System.out.println("connected!");
        // 这里先假设token是userId,后面会修改的
        Integer userId = Integer.parseInt(token);
        this.user = userMapper.selectById(userId);
        users.put(userId, this);
    }

    @OnClose
    public void onClose() {
        // 关闭链接
        System.out.println("disconnected!");
        if(this.user != null){
            users.remove(this.user.getId());
        }
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        // 从Client接收消息
        System.out.println("receive message!");
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    public void sendMessage(String message){
        // 加锁,异步通信
        synchronized (this.session){
            try{
                // 发送信息的api
                // 后端向当前链接发送信息
                this.session.getBasicRemote().sendText(message);
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

3.2、修改前端

  • 先添加一个全局变量类pk.js然后将这个添加到模块添加到index.js中
    AcWing—SpringBoot【学习笔记】(整合)_第175张图片
  • 然后修改PkIndexView.vue
    AcWing—SpringBoot【学习笔记】(整合)_第176张图片
  • 最后当一刷新页面的时候后端就会显示
    AcWing—SpringBoot【学习笔记】(整合)_第177张图片

3.3、增加JWT验证

  • 前端
    AcWing—SpringBoot【学习笔记】(整合)_第178张图片
  • 后端
    AcWing—SpringBoot【学习笔记】(整合)_第179张图片
    AcWing—SpringBoot【学习笔记】(整合)_第180张图片

3.4、前端页面

  • 先创建一个MatchGround.vue
    AcWing—SpringBoot【学习笔记】(整合)_第181张图片
    AcWing—SpringBoot【学习笔记】(整合)_第182张图片
  • 然后对按钮进行绑定事件
    AcWing—SpringBoot【学习笔记】(整合)_第183张图片
    AcWing—SpringBoot【学习笔记】(整合)_第184张图片
  • 实现一下傻瓜式的匹配
    AcWing—SpringBoot【学习笔记】(整合)_第185张图片
    AcWing—SpringBoot【学习笔记】(整合)_第186张图片
    AcWing—SpringBoot【学习笔记】(整合)_第187张图片
    AcWing—SpringBoot【学习笔记】(整合)_第188张图片
    AcWing—SpringBoot【学习笔记】(整合)_第189张图片
  • 将墙的创建移到后端,保证两个玩家进入的是同一个游戏界面

public class Game {
    final private Integer rows;
    final private Integer cols;
    final private Integer inner_walls_count;
    final int [][] g;
    final private static int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
    public Game(Integer rows, Integer cols, Integer inner_walls_count){
        this.rows = rows;
        this.cols = cols;
        this.inner_walls_count =inner_walls_count;
        this.g = new int[rows][cols];
    }

    public int[][] g(){
        return g;
    }

    private boolean check_connectivity(int sx, int sy, int tx, int ty){
        if(sx == tx && sy == ty) return true;
        g[sx][sy] = 1;
        for(int i = 0; i < 4; i++) {
            int x = sx + dx[i], y = sy + dy[i];
            if (x >= 0 && x < this.rows && y >= 0 && y < this.cols && g[x][y] == 0) {
                if (check_connectivity(x, y, tx, ty)) {
                    g[sx][sy] = 0;
                    return true;
                }
            }
        }
        return false;
    }
    private boolean draw() {
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.cols; j++) {
                g[i][j] = 0;
            }
        }

        // 给左右两边加上障碍物
        for (int r = 0; r < this.rows; r++) {
            g[r][0] = g[r][this.cols - 1] = 1;
        }

        // 给上下两边加上障碍物
        for (int c = 0; c < this.cols; c++) {
            g[0][c] = g[this.rows - 1][c] = 1;
        }

        Random random = new Random();
        // 随机生成障碍物
        for (int i = 0; i < this.inner_walls_count / 2; i++) {
            for (int j = 0; j < 1000; j++) {
                int r = random.nextInt(this.rows);
                int c = random.nextInt(this.cols);
                if (g[r][c] == 1 || g[this.rows - 1 - r][this.cols - 1 - c] == 1) {
                    continue;
                }
                if (r == this.rows - 2 && c == 1 || r == 1 && c == this.cols - 2) {
                    continue;
                }
                g[r][c] = g[this.rows - 1 - r][this.cols - 1 - c] = 1;
                break;
            }
        }
        return check_connectivity(this.rows - 2, 1, 1, this.cols - 2);
    }

    public void createMap(){
        for (int i = 0; i < 1000; i++) {
            if(draw()){
                break;
            }
        }
    }
}

AcWing—SpringBoot【学习笔记】(整合)_第190张图片
AcWing—SpringBoot【学习笔记】(整合)_第191张图片

  • 然后在前端修改

AcWing—SpringBoot【学习笔记】(整合)_第192张图片

AcWing—SpringBoot【学习笔记】(整合)_第193张图片
AcWing—SpringBoot【学习笔记】(整合)_第194张图片
AcWing—SpringBoot【学习笔记】(整合)_第195张图片

  • 最后使得两个玩家收到的地图是从后端统一传过来的地图

4、游戏同步策略

4.1、坐标同步

AcWing—SpringBoot【学习笔记】(整合)_第196张图片

AcWing—SpringBoot【学习笔记】(整合)_第197张图片
AcWing—SpringBoot【学习笔记】(整合)_第198张图片
AcWing—SpringBoot【学习笔记】(整合)_第199张图片
AcWing—SpringBoot【学习笔记】(整合)_第200张图片

4.2、多线程修改

AcWing—SpringBoot【学习笔记】(整合)_第201张图片

  • 使用了多线程,我具体也不太懂,反正是涉及到了多人处理同一件事情,有个先后,用这个。。。,这里
    AcWing—SpringBoot【学习笔记】(整合)_第202张图片
    AcWing—SpringBoot【学习笔记】(整合)_第203张图片
    AcWing—SpringBoot【学习笔记】(整合)_第204张图片
    AcWing—SpringBoot【学习笔记】(整合)_第205张图片
    AcWing—SpringBoot【学习笔记】(整合)_第206张图片
  • 然后开始修改前后端,先建立起通信
    AcWing—SpringBoot【学习笔记】(整合)_第207张图片
    AcWing—SpringBoot【学习笔记】(整合)_第208张图片

AcWing—SpringBoot【学习笔记】(整合)_第209张图片
AcWing—SpringBoot【学习笔记】(整合)_第210张图片
AcWing—SpringBoot【学习笔记】(整合)_第211张图片

AcWing—SpringBoot【学习笔记】(整合)_第212张图片

AcWing—SpringBoot【学习笔记】(整合)_第213张图片

  • 这样就可以简单的实现两用户操作蛇移动,都同步显示
  • 为了游戏的公平起见,要将判断蛇合法与否写到后端
  • 先将蛇的身体得到
    AcWing—SpringBoot【学习笔记】(整合)_第214张图片
    AcWing—SpringBoot【学习笔记】(整合)_第215张图片
    AcWing—SpringBoot【学习笔记】(整合)_第216张图片
    AcWing—SpringBoot【学习笔记】(整合)_第217张图片
  • 然后利用蛇的身体进行一系列的合法检测
    AcWing—SpringBoot【学习笔记】(整合)_第218张图片
    AcWing—SpringBoot【学习笔记】(整合)_第219张图片

4.3、前端结束弹窗

AcWing—SpringBoot【学习笔记】(整合)_第220张图片
AcWing—SpringBoot【学习笔记】(整合)_第221张图片

  • 然后判断Loser用户显示不同的页面
    AcWing—SpringBoot【学习笔记】(整合)_第222张图片
    AcWing—SpringBoot【学习笔记】(整合)_第223张图片
    AcWing—SpringBoot【学习笔记】(整合)_第224张图片
    AcWing—SpringBoot【学习笔记】(整合)_第225张图片
    AcWing—SpringBoot【学习笔记】(整合)_第226张图片
  • 再来一局逻辑修改
    AcWing—SpringBoot【学习笔记】(整合)_第227张图片

4.4、增加数据库

新建表

CREATE TABLE `kob`.`record`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `a_id` int NULL,
  `a_sx` int NULL,
  `a_sy` int NULL,
  `b_id` int NULL,
  `b_sx` int NULL,
  `b_sy` int NULL,
  `a_steps` varchar(1000) NULL,
  `b_steps` varchar(1000) NULL,
  `map` varchar(1000) NULL,
  `loser` varchar(10) NULL,
  `createtime` datetime NULL,
  PRIMARY KEY (`id`)
);

  • 新建pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Record {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private Integer aId;
    private Integer aSx;
    private Integer aSy;
    private Integer bId;
    private Integer bSx;
    private Integer bSy;
    private String aSteps;
    private String bSteps;
    private String map;
    private String loser;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    private Date createtime;
}

AcWing—SpringBoot【学习笔记】(整合)_第228张图片

  • 新建mapper
    AcWing—SpringBoot【学习笔记】(整合)_第229张图片
  • 注入
    AcWing—SpringBoot【学习笔记】(整合)_第230张图片
    AcWing—SpringBoot【学习笔记】(整合)_第231张图片
    在这里插入图片描述

5、微服务-匹配策略的实现

前面使用的匹配策略是一个傻瓜式的匹配,这里要实现用一个微服务来完成匹配的任务

AcWing—SpringBoot【学习笔记】(整合)_第232张图片

5.1、创建项目及配置环境

  • 新建一个springboot项目(backendspring),删除掉src目录,导入springcloud依赖,然后创建一个springboot子模块(matchingsystem),将backendspring中的pom.xml中的依赖剪切到matchingsystem中去
依赖名称 作用
spring-cloud-dependencies springcloud的依赖

AcWing—SpringBoot【学习笔记】(整合)_第233张图片

  • 然后创建业务
    AcWing—SpringBoot【学习笔记】(整合)_第234张图片
    AcWing—SpringBoot【学习笔记】(整合)_第235张图片

AcWing—SpringBoot【学习笔记】(整合)_第236张图片

  • 然后配置Spring Security来做权限访问,只能让本机的访问
    在这里插入图片描述
    AcWing—SpringBoot【学习笔记】(整合)_第237张图片

5.2、实现两个项目间通信

  • 再创建一个子模块(backend),将前面写好的backend中的src文件及pom.xml中的依赖都复制到新创建的这里面
    AcWing—SpringBoot【学习笔记】(整合)_第238张图片
  • 将之前开始匹配的代码抽出来做成一个函数
    AcWing—SpringBoot【学习笔记】(整合)_第239张图片
  • 这个时候当前端传回来消息的时候,去调用这两个方法,然后他们再去调用微服务里面的对应的接口
    AcWing—SpringBoot【学习笔记】(整合)_第240张图片
  • 这里需要加一个类,并且让WebSocketService注入
    AcWing—SpringBoot【学习笔记】(整合)_第241张图片
    AcWing—SpringBoot【学习笔记】(整合)_第242张图片
    AcWing—SpringBoot【学习笔记】(整合)_第243张图片
  • 这样就可以在前端点击页面的时候,可以看到匹配服务的控制台显示出来
    AcWing—SpringBoot【学习笔记】(整合)_第244张图片

5.3、匹配服务的编写

  • 这里又又又用到了线程,很烦,很后悔之前的操统没好好学
    AcWing—SpringBoot【学习笔记】(整合)_第245张图片
    AcWing—SpringBoot【学习笔记】(整合)_第246张图片
    AcWing—SpringBoot【学习笔记】(整合)_第247张图片
  • 写具体的匹配策略
    在这里插入图片描述
    AcWing—SpringBoot【学习笔记】(整合)_第248张图片
    AcWing—SpringBoot【学习笔记】(整合)_第249张图片
  • 然后编写后端接口(接受匹配服务的结果)
    AcWing—SpringBoot【学习笔记】(整合)_第250张图片
    AcWing—SpringBoot【学习笔记】(整合)_第251张图片
    AcWing—SpringBoot【学习笔记】(整合)_第252张图片
  • 然后在匹配服务返回结果
  • 先实现这个类
    AcWing—SpringBoot【学习笔记】(整合)_第253张图片
    AcWing—SpringBoot【学习笔记】(整合)_第254张图片
    AcWing—SpringBoot【学习笔记】(整合)_第255张图片
  • 然后就可以重启服务然后在前端运行两个页面进行匹配测试了
    AcWing—SpringBoot【学习笔记】(整合)_第256张图片

6、实现微服务:Bot代码执行

6.1、创建项目配置依赖

  • 创建一个Botrunningsystem的子项目用来编写bot机器人的代码
    AcWing—SpringBoot【学习笔记】(整合)_第257张图片
  • 然后导入依赖,将matchingsystem的复制过来然后加一个joor-java-8依赖
依赖名 作用
joor-java-8 是一个java反射工具包, 极小(只有4个核心类)流式编程, 主要简化繁复的反射API使用, 没有另加反射缓存.

AcWing—SpringBoot【学习笔记】(整合)_第258张图片

6.2、准备工作

  • 先编写一个api,创建service,controller一套东西
    AcWing—SpringBoot【学习笔记】(整合)_第259张图片

  • 配置一下路由,放行本地的/bot/add/
    AcWing—SpringBoot【学习笔记】(整合)_第260张图片

  • 然后修改一下端口号
    AcWing—SpringBoot【学习笔记】(整合)_第261张图片

  • 在前端加一个可以选择谁比赛的选择框
    AcWing—SpringBoot【学习笔记】(整合)_第262张图片
    在这里插入图片描述

  • 然后需要在每个环节都需要botId,既要知道userId也要知道BotId是谁
    AcWing—SpringBoot【学习笔记】(整合)_第263张图片
    AcWing—SpringBoot【学习笔记】(整合)_第264张图片
    AcWing—SpringBoot【学习笔记】(整合)_第265张图片
    AcWing—SpringBoot【学习笔记】(整合)_第266张图片

AcWing—SpringBoot【学习笔记】(整合)_第267张图片
AcWing—SpringBoot【学习笔记】(整合)_第268张图片
AcWing—SpringBoot【学习笔记】(整合)_第269张图片

6.3、实现线程

AcWing—SpringBoot【学习笔记】(整合)_第270张图片
AcWing—SpringBoot【学习笔记】(整合)_第271张图片
AcWing—SpringBoot【学习笔记】(整合)_第272张图片
AcWing—SpringBoot【学习笔记】(整合)_第273张图片
AcWing—SpringBoot【学习笔记】(整合)_第274张图片
AcWing—SpringBoot【学习笔记】(整合)_第275张图片

  • 优化一下前端
    AcWing—SpringBoot【学习笔记】(整合)_第276张图片
  • 然后要实现后端接收到bot微服务发送过来的消息
    AcWing—SpringBoot【学习笔记】(整合)_第277张图片
    AcWing—SpringBoot【学习笔记】(整合)_第278张图片
  • 然后在bot端将bot的移动方向放回给后端
    AcWing—SpringBoot【学习笔记】(整合)_第279张图片
    AcWing—SpringBoot【学习笔记】(整合)_第280张图片
    AcWing—SpringBoot【学习笔记】(整合)_第281张图片

6.4、编写一个AI

package com.kob.botrunningsystem.utils;

import java.util.ArrayList;
import java.util.List;

public class Bot implements com.kob.botrunningsystem.utils.BotInterface{
    static class Cell{
        public int x, y;
        public Cell(int x, int y){
            this.x = x;
            this.y = y;
        }
    }
    @Override
    public Integer nextMove(String input) {
        String[] strs = input.split("#");
        int[][] g = new int[13][14];
        for (int i = 0, k = 0; i < 13; i++) {
            for (int j = 0; j < 14; j++, k++) {
                if(strs[0].charAt(k) == '1'){
                    g[i][j] = 1;
                }
            }
        }
        int aSx = Integer.parseInt(strs[1]), aSy = Integer.parseInt(strs[2]);
        int bSx = Integer.parseInt(strs[4]), bSy = Integer.parseInt(strs[5]);

        List<Cell> aCells = getCells(aSx, aSy, strs[3]);
        List<Cell> bCells = getCells(bSx, bSy, strs[6]);

        for (Cell c : aCells) {
            g[c.x][c.y] = 1;
        }
        for (Cell c : bCells) {
            g[c.x][c.y] = 1;
        }

        int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
        for (int i = 0; i < 4; i++) {
            int x = aCells.get(aCells.size() - 1).x + dx[i];
            int y = aCells.get(aCells.size() - 1).y + dy[i];
            if(x >= 0 && x < 13 && y >= 0 && y < 14 && g[x][y] == 0){
                System.out.println("方向是" + i);
                return i;
            }
        }
        return 0;
    }

    // 检测当前回合蛇的长度是否增加
    private boolean check_tail_increasing(int step){
        if(step <= 10) return true;
        return step % 3 == 1;
    }

    // 这里我理解的就是 前端需要去渲染蛇所以才那么复杂,
    // 后端只需要去描绘出来蛇在地图上的位置就可以
    public List<Cell> getCells(int sx, int sy, String steps){
        steps = steps.substring(1, steps.length() - 1);
        List<Cell> res = new ArrayList<>();
        int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
        int x = sx, y = sy;
        int step = 0;
        res.add(new Cell(x, y));
        for(int i = 0; i < steps.length(); i++){
            int d = steps.charAt(i) - '0';
            x += dx[d];
            y += dy[d];
            res.add(new Cell(x, y));
            if(!check_tail_increasing(++ step)){
                res.remove(0);
            }
        }
        return res;
    }
}

6.5、修改天梯积分

AcWing—SpringBoot【学习笔记】(整合)_第282张图片在这里插入图片描述

六、完成对战列表与排行榜页面

1、实现返回对局列表的api

  • 先配置分页的配置
    AcWing—SpringBoot【学习笔记】(整合)_第283张图片
  • 还是那一套,先写service接口,然后Impl,最后写一下Controller
    在这里插入图片描述

2、对局列表前端页面修改

  • 先测试一下
    AcWing—SpringBoot【学习笔记】(整合)_第284张图片
    AcWing—SpringBoot【学习笔记】(整合)_第285张图片
    AcWing—SpringBoot【学习笔记】(整合)_第286张图片

AcWing—SpringBoot【学习笔记】(整合)_第287张图片

3、对局录像回放

  • 先搞一个页面,因为对局录像和pk页面很像,所以就拿来了pk页面
    AcWing—SpringBoot【学习笔记】(整合)_第288张图片

  • 然后创建一个新的store
    AcWing—SpringBoot【学习笔记】(整合)_第289张图片

  • 然后配置路由,因为需要当你点开查看录像的时候,要挑战到新的RecordContentView的页面
    AcWing—SpringBoot【学习笔记】(整合)_第290张图片

  • 写一下点击查看录像的函数AcWing—SpringBoot【学习笔记】(整合)_第291张图片

  • 然后开始修改蛇移动的代码,看是那种方式
    AcWing—SpringBoot【学习笔记】(整合)_第292张图片

4、分页条编写

AcWing—SpringBoot【学习笔记】(整合)_第293张图片
AcWing—SpringBoot【学习笔记】(整合)_第294张图片
AcWing—SpringBoot【学习笔记】(整合)_第295张图片
AcWing—SpringBoot【学习笔记】(整合)_第296张图片

5、实现返回排行榜数据的api

在这里插入图片描述
AcWing—SpringBoot【学习笔记】(整合)_第297张图片

6、排行榜前端页面修改

  • 和对局列表的差不多
<template>
    <ContentField>
        <table class="table table-striped table-hover" style="text-align: center">
            <thead>
                <tr>
                    <th>玩家</th>
                    <th>天梯分</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="user in users" :key="user.id" >
                    <td>
                        <img :src="user.photo" alt="" class="record-user-photo">
                        &nbsp;
                        <span class="record-user-username">{{ user.username }}</span>
                    </td>
                    <td>
                        {{ user.rating }}
                    </td>
                    
                </tr>
            </tbody>
        </table>
        <nav aria-label="...">
            <ul class="pagination" style="float: right;">
                <li class="page-item" @click="click_page(-2)">
                    <a class="page-link" href="#" >前一页</a>
                </li>

            
                <li v-for="page in pages" :class="'page-item ' + page.is_active"  :key="page.number" @click="click_page(page.number)">
                    <a class="page-link" href="#" >{{ page.number }}</a>
                </li>

                <li class="page-item" @click="click_page(-1)">
                    <a class="page-link" href="#" >后一页</a>
                </li>
            </ul>
        </nav>
   </ContentField>
</template>

<script>
import ContentField from '../../components/ContentField';
import { useStore } from 'vuex';
import { ref } from 'vue';
import $ from 'jquery';

export default{
    components: {
        ContentField
    },
    setup(){
        const store = useStore();
        let current_page = 1;
        let users = ref([]);
        let total_users = 0;
        let pages = ref([]);

        const click_page = (page) =>{
                if(page === -2) page = current_page - 1;
                else if(page === -1) page = current_page + 1;
                let max_pages = parseInt(Math.ceil(total_users / 3));
                if(page >= 1 && page <= max_pages){
                    pull_page(page);
                }
            }

        const update_pages = () => {
            // ceil是向上取整的意思
            let max_pages = parseInt(Math.ceil(total_users / 3));
            let new_pages = [];
            for(let i = current_page - 2; i <= current_page + 2; i++){
                if(i >= 1 && i <= max_pages){
                    new_pages.push({
                        number: i,
                        is_active: i === current_page ? "active" : "",
                    });
                }
            }
            pages.value = new_pages;
        }

        // 拉取某一页的函数
        const pull_page = page =>{
            current_page = page;
            $.ajax({
            url: "http://127.0.0.1:3000/ranklist/getlist/",
            type: "get",
            data:{
                page,
            },
            headers: {
                Authorization: "Bearer " + store.state.user.token,
            },
            success(resp){
                users.value = resp.users;
                total_users = resp.users_count;
                update_pages();
            },
            error(resp){
                console.log(resp);
            }
        });
        }

        pull_page(current_page);

        return {
            users,
            pages,
            click_page
        }

    }
}
</script>

<style scoped>
img.record-user-photo{
    width: 5vh;
    border-radius: 50%;
}
</style>

7、增加Bot数量上限

AcWing—SpringBoot【学习笔记】(整合)_第298张图片

七、项目上线

1、服务器环境配置

  1. 首先你要拥有一台服务器,阿里云、腾讯云、华为云都可以(我的是腾讯云的)
  2. 然后登录到你的服务器中ssh [email protected] 若你已经登录过然后又重置了虚拟机要执行以下命令:ssh-keygen -R "你的远程服务器ip地址"
  3. 然后sudo su -,输入你的密码,然后创建acs用户,并分配给用户acs权限
	 adduser acs  # 创建用户acs
	 usermod -aG sudo acs  # 给用户acs分配sudo权限
  1. 然后返回AC Terminal去配置免密登录,就是先进入到.ssh(没有就自己创建),然后生成秘钥ssh-keygen,打开家目录下的.ssh/config(没有就自己创建),输入你想免密登录的服务器的信息,然后ssh-copy-id ServerName,输入完密码后,就可以通过ssh ServerName,直接登录到你的那台服务器上了
Host myserver1
	    HostName IP地址或域名
	    User 用户名
  1. 可以将AC Terminal中的祖传代码拷贝过去scp .bashrc .vimrc .tmux.conf server_name: # server_name需要换成自己配置的别名
  2. 然后登录到自己的服务器中,安装tmux和docker,打开tmux然后安装docker,然后参考docker安装
	sudo apt-get update
	sudo apt-get install tmux
  1. 然后将当前用户添加到docker用户组,为了避免每次使用docker命令都需要加上sudo权限,可以将当前用户加入安装中自动创建的docker用户组
	sudo usermod -aG docker $USER
执行完此操作后,需要退出服务器,再重新登录回来,才可以省去sudo权限。
  1. 回到AC Terminal中的/var/lib/acwing/docker/images中将django_lesson_1_0.tarscp到你的服务器中scp django_lesson_1_0.tar springboot_server:
  2. 然后配置一下本地的免密登录,打开git bash here,来到家目录中然后还是没有.ssh自己创建,然后进去后,生成秘钥ssh-keygenvim config然后输入
Host myserver1
	    HostName IP地址或域名
	    User 用户名

然后ssh-copy-id ServerName,输入完密码后,就可以通过ssh ServerName,直接登录到你的那台服务器上,若你已经登录过然后又重置了虚拟机要执行以下命令:ssh-keygen -R "你的远程服务器ip地址"

  1. 然后从AC Terminal中登录到你的服务器上解压django_lesson_1_0.tar,查看镜像是否解压完成docker images
	docker load -i django_lesson_1_0.tar
  1. 然后创建容器docker run -p 20000:22 -p 443:443 -p 80:80 -p 3000:3000 -p 3001:3001 -p 3002:3002 -itd --name kob_server django_lesson:1.0,通过2000端口登录容器,后面的就是各种服务的端口,然后给这个容器起一个名字kob_server 最后加上镜像名字和版本号

  2. 然后进入到你的容器里面docker attach kob_server,然后在容器里面创建一套刚才的用户见上方操作,创建好新的acs用户后Ctrl+P Ctrl+q将容器挂起(这样容器不会关闭),然后进入云平台去放开20000端口
    AcWing—SpringBoot【学习笔记】(整合)_第299张图片

  3. 回到AC Terminal中配置免密登录,然后ssh-copy-id ServerName上,ssh ServerName到你的容器中,然后在本地配置免密登录步骤相同,然后把祖传代码传到容器里面scp .tmux.conf .bashrc .vimrc springboot:

Host springboot
     HostName ip
     User acs
     Port 20000
  1. 然后安装、配置mysql
更新一下: sudo apt-get update
安装: sudo apt-get install mysql-server
启动: sudo service mysql start
登录mysql: sudo mysql -u root
设置root密码:ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'yourpasswd';
  1. 然后创建数据库:
	create databaseskob;
	use kob;

创建表:m将以下sql语句复制到一个sql文件夹里面,然后通过命令在数据库中执行以下命令这个脚本就可了source /home/acs/create_table.sql

create table kob.bot
(
   id          int auto_increment
       primary key,
   user_id     int            not null,
   title       varchar(100)   null,
   description varchar(300)   null,
   content     varchar(10000) null,
   createtime  datetime       null,
   modifytime  datetime       null
);

create table kob.record
(
   id         int auto_increment
       primary key,
   a_id       int           null,
   a_sx       int           null,
   a_sy       int           null,
   b_id       int           null,
   b_sx       int           null,
   b_sy       int           null,
   a_steps    varchar(1000) null,
   b_steps    varchar(1000) null,
   map        varchar(1000) null,
   loser      varchar(10)   null,
   createtime datetime      null
);

create table kob.user
(
   id       int auto_increment
       primary key,
   username varchar(100)     null,
   password varchar(100)     null,
   photo    varchar(1000)    null,
   rating   int default 1500 null
);

AcWing—SpringBoot【学习笔记】(整合)_第300张图片
16. 然后安装java8,sudo apt-get install openjdk-8-jdk
在这里插入图片描述

2、打包后端

  • 修改一下后端
    AcWing—SpringBoot【学习笔记】(整合)_第301张图片
    AcWing—SpringBoot【学习笔记】(整合)_第302张图片
    AcWing—SpringBoot【学习笔记】(整合)_第303张图片
    然后把前端那个botdiamante替换成新的,大题意思就是换了一个接口,为了部署到服务器调用函数的参数需要那个读文件的方式
  • 然后把那个前后端的排行榜的分页换成10
  • 然后加一个显示你在那里,要不然分不清自己是那条蛇
    AcWing—SpringBoot【学习笔记】(整合)_第304张图片
  • 修改一下api,下面的每一个都修改一下
    AcWing—SpringBoot【学习笔记】(整合)_第305张图片
    AcWing—SpringBoot【学习笔记】(整合)_第306张图片
  • 然后打包后端,给三个子项目的pom.xml中加都加上
<packaging>jarpackaging>


<build>
    <plugins>
         <plugin>
             <groupId>org.springframework.bootgroupId>
             <artifactId>spring-boot-maven-pluginartifactId>
             
             <configuration>
                 <mainClass>这里要修改成main方法的路径mainClass>
             configuration>
             <executions>
                 <execution>
                     <goals>
                         <goal>repackagegoal>
                     goals>
                 execution>
             executions>
         plugin>
     plugins>
build>
  • 然后maven打包,这里我遇到了一个问题,参考这个

AcWing—SpringBoot【学习笔记】(整合)_第307张图片
AcWing—SpringBoot【学习笔记】(整合)_第308张图片

  • 然后将生成的三个jar包传到服务器上
    AcWing—SpringBoot【学习笔记】(整合)_第309张图片

AcWing—SpringBoot【学习笔记】(整合)_第310张图片

  • 然后在创建几个文件夹,然后把三个jar包放进去
    AcWing—SpringBoot【学习笔记】(整合)_第311张图片
  • 然后开三个分屏把项目分别启动,命令如下:java -jar jar包名字
    AcWing—SpringBoot【学习笔记】(整合)_第312张图片
    AcWing—SpringBoot【学习笔记】(整合)_第313张图片
  • 然后创建以下内容
  • AcWing—SpringBoot【学习笔记】(整合)_第314张图片

AcWing—SpringBoot【学习笔记】(整合)_第315张图片
AcWing—SpringBoot【学习笔记】(整合)_第316张图片
AcWing—SpringBoot【学习笔记】(整合)_第317张图片

  • 然后启动nginx,sudo /etc/init.d/nginx start
    在这里插入图片描述
  • 打开y总分配的域名
    AcWing—SpringBoot【学习笔记】(整合)_第318张图片

3、打包前端

  • 将前端里面所有的12.0.0.1换成https://app****.acapp.acwing.com.cn/api/ websocket那个ws要变成wss然后后面吗不要加api那个
  • 然后打包前端项目,通过vue的脚手架打包将生成的dist文件上传到服务器中
    AcWing—SpringBoot【学习笔记】(整合)_第319张图片
    AcWing—SpringBoot【学习笔记】(整合)_第320张图片
  • 将dist中的文件复制出来
    AcWing—SpringBoot【学习笔记】(整合)_第321张图片

八、实现AcApp端

1、准备工作

  • 检查一下vue的版本vue --version,如果是低于5.0.8就重新装一下高版本的,npm i -g @vue/[email protected]
  • 配置一下nginx,将下面的配置文件覆盖掉/etc/nginx/nginx.config,然后记得重新加载一下配置sudo /etc/init.d/nginx reload,加载完了以后可以在本地启动一下vue的项目然后试一下,如果可以正常显示就是跨域问题解决了
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
    # multi_accept on;
}

http {

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    gzip on;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;

     server {
         listen 80;
         server_name app3935.acapp.acwing.com.cn;
         rewrite ^(.*)$ https://${server_name}$1 permanent;
     }

    server {
        listen 443 ssl;
        server_name app3935.acapp.acwing.com.cn;
        ssl_certificate   cert/acapp.pem;
        ssl_certificate_key  cert/acapp.key;
        ssl_session_timeout 5m;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        charset utf-8;
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        client_max_body_size 10M;

        location /api {
            proxy_pass http://127.0.0.1:3000;
        }

        location /websocket {
            proxy_pass http://127.0.0.1:3000;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_read_timeout  36000s;
        }

        location / {
            root /home/acs/kob/web;
            index index.html;
            try_files $uri $uri/ /index.html;
        }

        location /acapp {
            alias /home/acs/kob/acapp;
            if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Allow-Origin' 'https://www.acwing.com';
                add_header 'Access-Control-Allow-Methods' 'GET, PUT, OPTIONS, POST, DELETE';
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Amz-Date';
                add_header 'Access-Control-Max-Age' 86400;
                add_header 'Content-Type' 'text/html; charset=utf-8';
                add_header 'Content-Length' 0;
                return 204;
            }
            if ($request_method = 'PUT') {
                add_header 'Access-Control-Allow-Origin' 'https://www.acwing.com';
                add_header 'Access-Control-Allow-Methods' 'GET, PUT, OPTIONS, POST, DELETE';
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Amz-Date';
                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
            }
            if ($request_method = 'GET') {
                add_header 'Access-Control-Allow-Origin' 'https://www.acwing.com';
                add_header 'Access-Control-Allow-Methods' 'GET, PUT, OPTIONS, POST, DELETE';
                add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Amz-Date';
                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
            }
        }
    }
}
  • 然后创建一个新的vue项目添加插件vuevue-router
    AcWing—SpringBoot【学习笔记】(整合)_第322张图片
  • 然后添加几个依赖bootstrap@properjs/corevue3-ace-editorjquery
    AcWing—SpringBoot【学习笔记】(整合)_第323张图片
  • 然后配置文件vue.config.js,可以让vue3将项目打包成一个js文件和一个css文件。
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  configureWebpack: {
    // No need for splitting
    optimization: {
      splitChunks: false
    }
  }
})
  • 将web项目中的src复制到acapp中,然后运行项目看看是否能正常运行,如果可以运行的话,就打包一下,还是从那个build
    AcWing—SpringBoot【学习笔记】(整合)_第324张图片
  • 将生成的dist文件中的js和css中的*.js和**.css传入到服务器springboot:kob/acapp中。然后统一改一下名字变成app.js和app.css
    AcWing—SpringBoot【学习笔记】(整合)_第325张图片
    AcWing—SpringBoot【学习笔记】(整合)_第326张图片

2、测试

  • 然后可以访问一下https://app****.acapp.acwing.com.cn/acapp/app.js,可以看一下这个
    AcWing—SpringBoot【学习笔记】(整合)_第327张图片
    注:如果后面F12控制台报错myfunc不是一个函数就是将最后的一个括号删除了
  • 然后修改一下
    AcWing—SpringBoot【学习笔记】(整合)_第328张图片
    AcWing—SpringBoot【学习笔记】(整合)_第329张图片
  • 将css和js的地址放这里面
    AcWing—SpringBoot【学习笔记】(整合)_第330张图片
  • 创建两个脚本来快速的更新js和css,如果要更新前端的项目就可以使用脚本来快速的上传到服务器上了
    rename.sh(在kob/acapp中)
#! /bin/bash

rm app.js app.css

mv *.js app.js
mv *.css app.css

updateload.sh(在本地文件的King Of Bots\kob\acapp中)

#! /bin/bash

scp dist/js/*.js springboot:kob/acapp/
scp dist/css/*.css springboot:kob/acapp/

ssh springboot 'cd kob/acapp && ./rename.sh'
  • 然后将修改后的js和css,通过执行脚本进行更新,可以看到修改后的内容,再次修改应用界面
    AcWing—SpringBoot【学习笔记】(整合)_第331张图片
    AcWing—SpringBoot【学习笔记】(整合)_第332张图片
  • 上传一张图片 然后,点击提交,并打开应用,Ctrl + Shift + R清空缓存
    AcWing—SpringBoot【学习笔记】(整合)_第333张图片

3、整改前端代码

这里我们发现我们的背景图片都没有正常的显示,是因为依赖和acwing里面的样式有冲突,所以要修改一下前端的样式

  • 先要把UserAccountLogiView.vue中的一部分代码放到App.vue中
    AcWing—SpringBoot【学习笔记】(整合)_第334张图片
    AcWing—SpringBoot【学习笔记】(整合)_第335张图片

AcWing—SpringBoot【学习笔记】(整合)_第336张图片

  • 不行了不记了,直接去AcGit上面看吧AcGit,前端人都快改傻了

九、AcApp与Web端的第三方授权登录

AcWing—SpringBoot【学习笔记】(整合)_第337张图片

1、AcApp端第三方登录代码实现

  • 先导入依赖httpclientspring-boot-starter-data-redis,放到pom.xml中
    AcWing—SpringBoot【学习笔记】(整合)_第338张图片

  • 然后编写两个接口,service、controller,然后放行
    AcWing—SpringBoot【学习笔记】(整合)_第339张图片
    AcWing—SpringBoot【学习笔记】(整合)_第340张图片
    AcWing—SpringBoot【学习笔记】(整合)_第341张图片

package com.kob.backend.service.impl.user.account.acwing;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.user.account.acwing.utils.HttpCilentUtil;
import com.kob.backend.service.user.account.acwing.AcAppService;
import com.kob.backend.utils.JwtUtil;
import com.sun.org.glassfish.gmbal.NameValue;
import org.apache.http.HttpHeaders;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Duration;
import java.util.*;

@Service
public class AcAppServiceImpl implements AcAppService {
    private final static String appid = "4296";
    private final static String appsecret = "11ad11a17a094434ba56b152b3f1bec7";
    private final static String redirectUri = "https://app4296.acapp.acwing.com.cn/api/user/account/acwing/acapp/receive_code/";
    private final static String applyAccessTokenUrl = "https://www.acwing.com/third_party/api/oauth2/access_token/";
    private final static String getUserInfoUrl = "https://www.acwing.com/third_party/api/meta/identity/getinfo/";
    private final static Random random = new Random();

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Override
    public JSONObject applyCode() {
       JSONObject resp = new JSONObject();
       resp.put("appid", appid);
        try {
            resp.put("redirect_uri", URLEncoder.encode(redirectUri, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            resp.put("result", "failed");
            return resp;
        }
        resp.put("scope", "userinfo");
        StringBuilder state = new StringBuilder();
        for (int i = 0;i < 10; i++)
            state.append((char) (random.nextInt(10) + '0'));
        resp.put("state", state.toString());
        resp.put("result", "success");
        // 将state存储到redis中,后面供返回回来的state做比对,防止野鸡服务器
        redisTemplate.opsForValue().set(state.toString(), "true");
        // 设置一下10分钟过期
        redisTemplate.expire(state.toString(), Duration.ofMinutes(10));
        return resp;
    }
    @Override
    public JSONObject receiveCode(String code, String state) {
        JSONObject resp = new JSONObject();
        resp.put("result", "failed");
        if (code == null || state == null) return resp;
        if (Boolean.FALSE.equals(redisTemplate.hasKey(state))) return resp;
        redisTemplate.delete(state);
        List<NameValuePair> nameValuePairs = new LinkedList<>();
        nameValuePairs.add(new BasicNameValuePair("appid", appid));
        nameValuePairs.add(new BasicNameValuePair("secret", appsecret));
        nameValuePairs.add(new BasicNameValuePair("code", code));

        String getString = HttpCilentUtil.get(applyAccessTokenUrl, nameValuePairs);
        if (getString == null) return resp;
        JSONObject getResp = JSONObject.parseObject(getString);
        String accessToken = getResp.getString("access_token");
        String openid = getResp.getString("openid");
        if (accessToken == null || openid == null) return resp;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("openid", openid);
        List<User> users = userMapper.selectList(queryWrapper);
        if(!users.isEmpty()){
            User user = users.get(0);
            String jwt = JwtUtil.createJWT(user.getId().toString());
            resp.put("result", "success");
            resp.put("jwt_token", jwt);
            return resp;
        }

        nameValuePairs = new LinkedList<>();
        nameValuePairs.add(new BasicNameValuePair("access_token", accessToken));
        nameValuePairs.add(new BasicNameValuePair("openid", openid));
        getString = HttpCilentUtil.get(getUserInfoUrl, nameValuePairs);
        if (getString == null) return resp;
        getResp = JSONObject.parseObject(getString);
        String username = getResp.getString("username");
        String photo = getResp.getString("photo");

        User user = new User(
                null,
                username,
                null,
                photo,
                1500,
                openid
        );
        userMapper.insert(user);
        String jwt = JwtUtil.createJWT(user.getId().toString());
        resp.put("result", "success");
        resp.put("jwt_token", jwt);
        return resp;
    }
}

  • 要给数据库中user中添加一列,alter table user add column openid varchar(200);,然后对应修改pojo
    AcWing—SpringBoot【学习笔记】(整合)_第342张图片

  • 然后要修改一下前端代码
    AcWing—SpringBoot【学习笔记】(整合)_第343张图片

  • 这里要上传前端代码的时候,不仅要加那个myappid还要加一个AcWingOS
    AcWing—SpringBoot【学习笔记】(整合)_第344张图片
    AcWing—SpringBoot【学习笔记】(整合)_第345张图片

  • 修改一下后端
    AcWing—SpringBoot【学习笔记】(整合)_第346张图片

  • 然后可以打包前后端,上传到服务器中,测试

2、Web端第三方登录代码实现

AcWing—SpringBoot【学习笔记】(整合)_第347张图片

  • 后端和acapp的就改一改就行
package com.kob.backend.service.impl.user.account.acwing;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.user.account.acwing.utils.HttpCilentUtil;
import com.kob.backend.service.user.account.acwing.WebService;
import com.kob.backend.utils.JwtUtil;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Duration;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

@Service
public class WebAppServiceImpl implements WebService {
    private final static String appid = "4296";
    private final static String appsecret = "11ad11a17a094434ba56b152b3f1bec7";
    private final static String redirectUri = "https://app4296.acapp.acwing.com.cn/user/account/acwing/web/receive_code/";
    private final static String applyAccessTokenUrl = "https://www.acwing.com/third_party/api/oauth2/access_token/";
    private final static String getUserInfoUrl = "https://www.acwing.com/third_party/api/meta/identity/getinfo/";
    private final static Random random = new Random();

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Override
    public JSONObject applyCode() {

        JSONObject resp = new JSONObject();
        String encodeUrl = "";
        try {
            encodeUrl = URLEncoder.encode(redirectUri, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            resp.put("result", "failed");
            return resp;
        }
        StringBuilder state = new StringBuilder();
        for (int i = 0;i < 10; i++)
            state.append((char) (random.nextInt(10) + '0'));
        resp.put("state", state.toString());
        resp.put("result", "success");
        // 将state存储到redis中,后面供返回回来的state做比对,防止野鸡服务器
        redisTemplate.opsForValue().set(state.toString(), "true");
        // 设置一下10分钟过期
        redisTemplate.expire(state.toString(), Duration.ofMinutes(10));
        String applyCodeUrl = "https://www.acwing.com/third_party/api/oauth2/web/authorize/?appid=" + appid
                + "&redirect_uri=" + encodeUrl + "&scope=" + "userinfo" + "&state=" + state;
        resp.put("apply_code_url", applyCodeUrl);
        return resp;
    }

    @Override
    public JSONObject receiveCode(String code, String state) {
        JSONObject resp = new JSONObject();
        resp.put("result", "failed");
        if (code == null || state == null) return resp;
        if (Boolean.FALSE.equals(redisTemplate.hasKey(state))) return resp;
        redisTemplate.delete(state);
        List<NameValuePair> nameValuePairs = new LinkedList<>();
        nameValuePairs.add(new BasicNameValuePair("appid", appid));
        nameValuePairs.add(new BasicNameValuePair("secret", appsecret));
        nameValuePairs.add(new BasicNameValuePair("code", code));

        String getString = HttpCilentUtil.get(applyAccessTokenUrl, nameValuePairs);
        if (getString == null) return resp;
        JSONObject getResp = JSONObject.parseObject(getString);
        String accessToken = getResp.getString("access_token");
        String openid = getResp.getString("openid");
        if (accessToken == null || openid == null) return resp;

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("openid", openid);
        List<User> users = userMapper.selectList(queryWrapper);
        if(!users.isEmpty()){
            User user = users.get(0);
            String jwt = JwtUtil.createJWT(user.getId().toString());
            resp.put("result", "success");
            resp.put("jwt_token", jwt);
            return resp;
        }

        nameValuePairs = new LinkedList<>();
        nameValuePairs.add(new BasicNameValuePair("access_token", accessToken));
        nameValuePairs.add(new BasicNameValuePair("openid", openid));
        getString = HttpCilentUtil.get(getUserInfoUrl, nameValuePairs);
        if (getString == null) return resp;
        getResp = JSONObject.parseObject(getString);
        String username = getResp.getString("username");
        String photo = getResp.getString("photo");

        if(username == null || photo == null){
            return resp;
        }

        for(int i = 0; i < 100; i++){
            QueryWrapper<User> usernamequeryWrapper = new QueryWrapper<>();
            usernamequeryWrapper.eq("username", username);
            if(userMapper.selectList(usernamequeryWrapper).isEmpty()) break;
            username += (char)(random.nextInt(10) + '0');
            if(i == 99) return resp;
        }

        User user = new User(
                null,
                username,
                null,
                photo,
                1500,
                openid
        );
        userMapper.insert(user);
        String jwt = JwtUtil.createJWT(user.getId().toString());
        resp.put("result", "success");
        resp.put("jwt_token", jwt);
        return resp;
    }
}

  • 然后修改一下前端代码
    AcWing—SpringBoot【学习笔记】(整合)_第348张图片
    AcWing—SpringBoot【学习笔记】(整合)_第349张图片

AcWing—SpringBoot【学习笔记】(整合)_第350张图片
AcWing—SpringBoot【学习笔记】(整合)_第351张图片

  • 然后就完结了!撒花!

完整代码地址https://git.acwing.com/lzy612/kob

AcWing—SpringBoot【学习笔记】(整合)_第352张图片

你可能感兴趣的:(#,springboot,spring,boot,git)