陪玩小项目努力

青青陪玩

一、项目框架

二、搭建项目

1、新建项目

2、新建Model模块

3、引入依赖

复制代码
        <!--数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
            <scope>runtime</scope>
        </dependency>
        <!--Druid 连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.20</version>
        </dependency>
        <!--MyBatisPlus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.14</version>
        </dependency>
        <!--FastJson2-->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.40</version>
        </dependency>
        
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
                <!--分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-autoconfigure</artifactId>
            <version>1.4.6</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.6</version>
        </dependency>
​
        <!--AOP-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

4、复制上一个项目可以使用的工具类等

5、修改mobile-api的依赖 pom.xml

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.javasm</groupId>
        <artifactId>qingqing</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
​
    <artifactId>mobile-api</artifactId>
​
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
​
    <dependencies>
        <dependency>
            <groupId>com.javasm</groupId>
            <artifactId>common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
​
</project>

三、介绍项目模板

1、找到代码

2、配置文件yml

复制代码
server:
  tomcat:
    max-swallow-size: 10MB
    max-http-form-post-size: 10MB
  port: 8080
spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB
  application:
    name: mobile_api
    datasource:
      url: jdbc:mysql://127.0.0.1:3306/qingqing?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: root
      type: com.alibaba.druid.pool.DruidDataSource
      druid:
        initial-size: 5
        min-idle: 10
        max-active: 100
  data:
    redis:
      host: localhost
      port: 6379
      password: javasm
      database: 2
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3、vite.config.js

这个文件,仅仅限于在本地使用vite启动项目的时候生效。

当前项目,如果发布到Linux服务器上,使用Nginx发布的话,配置文件失效,要重新配置

复制代码
import { fileURLToPath, URL } from 'node:url'
​
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
​
export default defineConfig({
  plugins: [vue()],
  //配置根路径,这个不配置会导致部署之后访问不到
  base: './',
  //  构建
  build: {
    outDir: 'dist', //指定打包输出路径
    assetsDir: 'assets', //指定静态资源存放路径
    cssCodeSplit: true, //css代码拆分,禁用则所有样式保存在一个css里面
    sourcemap: false, //是否构建source map 文件
​
    // 生产环境取消 console
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    },
​
    //会打包出 css js 等文件夹 目录显得清晰
    rollupOptions: {
      output: {
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: '[ext]/[name]-[hash].[ext]'
      }
    }
  },
  resolve: {
    alias: {
      //别名,给./src目录,起个别名@,在文件中就可以使用@替换src了
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  // 本地服务
  server: {
    host: "0.0.0.0", // ip
    port: 5173,  // 端口号
    open: false,  // 是否自动在浏览器打开
    https: false, // 是否开启 https
    cors: true, // 允许跨域
    proxy: {
      //之前访问服务端接口:http://localhost:8080/login/doLogin
      //这样配置之后;http://localhost:5173/web-dev-api/login/doLogin
      //内部看见了/web-dev-api转发,到http://localhost:8080
      //http://localhost:8080/login/doLogin
      '/web-dev-api': {
        //本地IP
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (p) => p.replace(/^\/web-dev-api/, '')
      },
      '/web-api': {
        //线上服务器地址
        target: 'http://121.36.72.48:8080',
        changeOrigin: true,
        rewrite: (p) => p.replace(/^\/web-api/, '')
      },
    }
  }
})
​

3、环境配置

vite项目,会根据环境,来自动选择哪个配置文件。

开发阶段,会自动读取.evn.development配置文件

4、安装

5、启动

四、登录模块

已经有登录页面了,只需要写接口即可。

1、接口

登录模块:对方需要什么返回值,我需要什么参数,才能给他这个返回值。

页面能提供:用户名和密码

页面需要:用户基本信息,包括uid头像昵称等

先写接口、测试接口可用

写页面

页面对接接口

1.1 接口思路
  1. 创建一个Controller

    • LoginController 调用 LoginService ,LoginService去调用WebUserService,查询用户信息
  2. 新建用户名密码登录的方法

  3. 完善登录接口,doUsernameLogin

  4. 添加Redis缓存,改变请求的流程

  5. 添加,记录登录日志的功能

    • 每天,每个用户,只能记录1次

    • 记录每天第一次登录的时间,其余不记录

1.2 代码示例

从接口最开始的Controller开始写,后续缺什么补什么。

  • Controller
复制代码
@RestController
@RequestMapping("/login")
public class LoginController {
​
    @Resource
    LoginService loginService;
​
    @PostMapping("/doUsernameLogin")
    public R doUnameLogin(String username,String password){
        //检查参数合法性
        ParameterUtils.checkParameter(username,password);
        WebUser loginUser = loginService.doUnameLogin(username,password);
        return R.ok(loginUser);
    }
}
  • LoginService

第一形态

复制代码
@Service
public class LoginServiceImpl implements LoginService {
​
    @Resource
    WebUserService webUserService;
​
    @Override
    public WebUser doUnameLogin(String username, String password) {
        //不加Redis的情况下,纯mysql查询的业务完成之后,要暂停编码,开始测试
        //心里要确认一下,这一段的代码是没有问题的
        //根据用户名 查询用户信息
        WebUser loginUser = webUserService.getByUname(username);
        //如果用户信息是null ,说明数据库没有
        if (loginUser == null){
            throw new JavasmException(ExceptionEnum.User_Not_Found);
        }
        //如果有用户信息,校验密码是否正确
        //如果密码不正确,抛出异常
        if (!password.equals(loginUser.getPassword())){
            throw new JavasmException(ExceptionEnum.Password_Error);
        }
​
        //返回用户信息
        return loginUser;
    }
}

第二形态

复制代码
@Service
public class LoginServiceImpl implements LoginService {
​
    @Resource
    WebUserService webUserService;
    @Resource
    RedisTemplate<String,WebUser> redisTemplate;
​
    @Override
    public WebUser doUnameLogin(String username, String password) {
        //先从Redis中获取用户信息 weuser:username:%s
        //考虑到Key会反复使用,新建一个常量类,统一管理Redis的Key
        String key = String.format(RedisKeys.WebUserName,username);
        //从Redis中获取数据
        WebUser webUser = redisTemplate.opsForValue().get(key);
        if (webUser != null){
            if (webUser.getUid() == null){
                throw new JavasmException(ExceptionEnum.User_Not_Found);
            }
            //说明已经查询到了数据
            //检查密码
            //如果密码不正确,抛出异常
            if (!password.equals(webUser.getPassword())){
                throw new JavasmException(ExceptionEnum.Password_Error);
            }
            //刷新一下Redis的过期时间
            redisTemplate.expire(key,15, TimeUnit.DAYS);
            return webUser;
        }
        //Redis中获取不到数据了
        //根据用户名 查询用户信息
        WebUser loginUser = webUserService.getByUname(username);
        //如果用户信息是null ,说明数据库没有
        if (loginUser == null){
            redisTemplate.opsForValue().set(key,new WebUser(),10,TimeUnit.MINUTES);
            throw new JavasmException(ExceptionEnum.User_Not_Found);
        }
        //如果有用户信息,校验密码是否正确
        if (!password.equals(loginUser.getPassword())){
            throw new JavasmException(ExceptionEnum.Password_Error);
        }
        //登录成功了
        redisTemplate.opsForValue().set(key,loginUser,15,TimeUnit.DAYS);
​
        //返回用户信息
        return loginUser;
    }
}
​

第三形态

考虑到登录之后,需要头像和昵称

复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WebUser extends Model<WebUser> {
​
    @TableId(type = IdType.AUTO)
    private Integer uid;
    //用户名
    private String username;
    //密码
    private String password;
    //0正常1删除2封号
    private Integer status;
    //邮箱
    private String email;
    //手机号
    private String phone;
    //注册时间
    private Date regTime;
​
    @TableField(exist = false)
    private WebUserInfo userInfo;
​
}
复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WebUserInfo extends Model<WebUserInfo> {
    //用户编号
    @TableId(type = IdType.INPUT)
    private Integer uid;
    //用户名
    private String nickname;
    //用户vip等级
    private Integer vipGrade;
    //头像
    private String headPic;
    //贵族级别
    private Integer loadGrade;
    //是否大神用户 0普通用户 1大神用户
    private Integer godStatus;
    //粉丝数量
    private Integer fansNum;
    //IP归属地
    private String ipAddress;
    //个人说明
    private String remark;
    //喜欢的游戏说明
    private String hobbyGame;
    //喜欢的音乐说明
    private String hobbyMusic;
    //封停状态 0封停 1正常
    private String lockoutStatus;
    //解封时间
    private Date lockoutTime;
    //生日
    private String birthday;
    //城市
    private String city;
    //性别 0保密1男2女
    private Integer gender;
    //心情
    private String mood;
    //简介
    private String intro;
    //0自然人1ai
    private Integer isai;


}
复制代码
@Service
public class LoginServiceImpl implements LoginService {

    @Resource
    WebUserService webUserService;
    @Resource
    WebUserInfoService webUserInfoService;
    @Resource
    RedisTemplate<String,WebUser> redisTemplate;

    @Override
    public WebUser doUnameLogin(String username, String password) {
        //先从Redis中获取用户信息 weuser:username:%s
        //考虑到Key会反复使用,新建一个常量类,统一管理Redis的Key
        String key = String.format(RedisKeys.WebUserName,username);
        //从Redis中获取数据
        WebUser webUser = redisTemplate.opsForValue().get(key);
        if (webUser != null){
            if (webUser.getUid() == null){
                throw new JavasmException(ExceptionEnum.User_Not_Found);
            }
            //说明已经查询到了数据
            //检查密码
            //如果密码不正确,抛出异常
            if (!password.equals(webUser.getPassword())){
                throw new JavasmException(ExceptionEnum.Password_Error);
            }
            //刷新一下Redis的过期时间
            redisTemplate.expire(key,15, TimeUnit.DAYS);
            return webUser;
        }
        //Redis中获取不到数据了
        //根据用户名 查询用户信息
        WebUser loginUser = webUserService.getByUname(username);
        //如果用户信息是null ,说明数据库没有
        if (loginUser == null){
            redisTemplate.opsForValue().set(key,new WebUser(),10,TimeUnit.MINUTES);
            throw new JavasmException(ExceptionEnum.User_Not_Found);
        }
        //如果有用户信息,校验密码是否正确
        if (!password.equals(loginUser.getPassword())){
            throw new JavasmException(ExceptionEnum.Password_Error);
        }
        //登录成功之后,存入Redis之前,要查询出WebUserInfo的信息
        WebUserInfo userInfo = webUserInfoService.getById(loginUser.getUid());
        if (userInfo != null){
            loginUser.setUserInfo(userInfo);
        }
        //登录成功了
        redisTemplate.opsForValue().set(key,loginUser,15,TimeUnit.DAYS);

        //返回用户信息
        return loginUser;
    }
}
1.3 记录登录日志
  • 自定义注解
复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface WebLoginLog {
}
  • AOP
复制代码
@Component
@Aspect
public class WebLoginLogAspect {

    @Resource
    RedisTemplate<String,Integer> redisTemplate;

    @AfterReturning(
            value = "@annotation(com.javasm.qingqing.common.annotation.WebLoginLog)",
            returning = "webUser"
    )
    public void saveLog(JoinPoint joinPoint,Object webUser){
        if (webUser != null && webUser instanceof WebUser){
            //记录日志
            //记录日志的行为,不在主要流程里,
            // 日志是否记录以及是否记录成功,是否抛出异常,都不影响登录成功了
            new Thread(()->{
                WebUser user = (WebUser) webUser;
                Integer uid = user.getUid();
                //如何判断 今天是否已经记录过日志了
                //因为 登录接口访问的比较频繁,如果通过mysql判断数据是否已经存储了,会给数据库带来很大压力
                String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
                //key webuser:login:log:1006:2025-11-29
                String key = String.format(RedisKeys.WebLoginLog,uid,today);
                Integer redisUid = redisTemplate.opsForValue().get(key);
                if (redisUid == null){
                    //今天还没有存储过
                    WebUserLoginLog webUserLoginLog = new WebUserLoginLog();
                    webUserLoginLog.setUid(uid);
                    webUserLoginLog.setLoginTime(new Date());
                    webUserLoginLog.insert();
                    //redis中 记录今天登录信息
                    redisTemplate.opsForValue().set(key,uid,1, TimeUnit.DAYS);
                }
            }).start();
        }
    }
}
  • 使用注解
复制代码
    @WebLoginLog
    public WebUser doUnameLogin(String username, String password) 
  • RedisKeys
复制代码
public interface RedisKeys {
    //用户名查询用户信息:webuser:username:用户名
    String WebUserName = "webuser:username:%s";
    //每日登录记录:webuser:login:log:uid:2025-11-11
    String WebLoginLog = "webuser:login:log:%s:%s";

}

2、页面对接

复制代码
<template>
  <div class="login-main">
    <div class="login-head">
      <div class="row video-main">
        <div class="col-lg-12 col-md-12">
          <video class="head-video" src="http://cd.ray-live.cn/video/xiangxiang.mp4" poster="" autoplay="" loop="" muted=""></video>
        </div>
      </div>
    </div>
    <div class="container">
      <div class="row">
        <div class="col-lg-6 col-md-6 login-form-main">
          <div class="login-form">
            <h2>登录</h2>
            <form ref="formValid" class="needs-validation" novalidate>
              <div class="tab-content" id="nav-tabContent">
                <div class="tab-pane fade show active" id="nav-home" role="tabpanel" aria-labelledby="nav-home-tab" tabindex="0">
                  <div class="form-group">
                    <input type="text" class="form-control" placeholder="请输入用户名" v-model="form.uname" >
                  </div>
                  <div class="form-group input-group">
                    <input type="password" class="form-control" placeholder="请输入密码" v-model="form.pwd"  @keyup.enter="doLogin">
                  </div>
                </div>
                <div class="tab-pane fade" id="nav-profile" role="tabpanel" aria-labelledby="nav-profile-tab" tabindex="0">
                  <div class="form-group">
                    <input type="text" class="form-control" placeholder="请输入手机号" v-model="form.uname" required >
                  </div>
                  <div class="form-group input-group">
                    <input type="password" class="form-control" placeholder="请输入验证码" v-model="form.pwd"  required>
                    <input type="button" :value="countDown > 0 ? countDown+` s后重新发送`  : '发送验证码'" class="btn btn-info" :disabled="countDown > 0">
                  </div>
                </div>
              </div>

              <nav>
                <div class="nav nav-tabs" id="nav-tab" role="tablist">
                  <button class="nav-link active" id="nav-home-tab"
                          data-bs-toggle="tab" data-bs-target="#nav-home" type="button"
                          role="tab" aria-controls="nav-home" aria-selected="true" @click="form.type='uname'">用户名密码登录</button>
                  <button class="nav-link" id="nav-profile-tab"
                          data-bs-toggle="tab" data-bs-target="#nav-profile" type="button"
                          role="tab" aria-controls="nav-profile" aria-selected="false" @click="form.type='phone'">手机号验证码登录</button>
                </div>
              </nav>
              <button type="button" class="default-btn" @click="doLogin">登录</button>
              <div class="or-text"><span></span></div>
              <button type="button" class="google-btn" @click="jumpReg">注册</button>
            </form>
          </div>
        </div>
        <div class="col-lg-3 col-md-3 offset-3 login-logo">
          <img src="@/assets/imgs/logo_reg.png" alt="image">
        </div>
      </div>
    </div>
  </div>

</template>

<script setup>
import {ref, onMounted} from "vue";
import router from '@/router/index.js'

import api from '@/utils/request.js'

import userStore from '@/stores/userStore.js';

let form = ref({
  uname: '',
  pwd: '',
});
let countDown=ref(0);


let doLogin = () => {
  let param = {
    "username": form.value.uname,
    "password": form.value.pwd
  }

  api.post("/login/doUsernameLogin",param).then(result=>{
    //把登录返回的用户信息,存储到pina中,sessionStorage中
    userStore().userModel = result.data;
    router.push("/")
  })
}
let jumpReg=()=>{
  router.push("/reg")
}

onMounted(() => {

})
</script>

<style scoped>
.login-form input {
  margin: 20px 0px;
}

.vector-image img {
  margin-top: 145px;
}

.login-head {
  height: 490px;
  display: flex;
  justify-content: center;
  position: absolute;
  width: 100%;
  top: 0px;
  left: 0px;
  z-index: -1;
}

.login-head video {
  position: absolute;
  top: 0;
  left: 0;
  min-width: 100%; /* 保证视频宽度至少为 100% */
  min-height: 100%; /* 保证视频高度至少为 100% */
  width: auto;
  height: auto;
  z-index: -100;
  object-fit: cover;
}

.login-main {
  height: 100%;
  width: 99.9%;
  max-width: 1920px;
  min-height: 99vh;
  min-width: 1320px;
  max-height: 100%;
  position: absolute;
  z-index: -2;
  scrollbar-width: none;
  overflow: hidden;
}

.login-main .container {
  position: absolute;
  float: left;
  left: 25%;
  top: 10px;
  margin-top: 0px;
  z-index: 2;
  height: 690px;
}
.login-form-main{

  margin-top: 100px;
}
.login-logo{

}
.login-form{
  background: rgba(255,255,255,0.5);
}


/* 针对 WebKit 浏览器 */
::-webkit-scrollbar {
  display: none;
}

</style>

五、注册模块

1、接口

  • Controller
复制代码
    @PostMapping("/doReg")
    public R doRegister(@RequestBody WebUser webUser) {
        //参加合法校验
        ParameterUtils.checkParameter(
                webUser,
                webUser.getUserInfo(),
                webUser.getUsername(),
                webUser.getUserInfo().getNickname()
        );
        WebUser regUser = loginService.doRegister(webUser);
        return R.ok(regUser);
    }
  • Service
复制代码
package com.javasm.qingqing.login.service.impl;

import com.javasm.qingqing.common.annotation.WebLoginLog;
import com.javasm.qingqing.common.container.RedisKeys;
import com.javasm.qingqing.common.exception.ExceptionEnum;
import com.javasm.qingqing.common.exception.JavasmException;
import com.javasm.qingqing.common.utils.IpAddressUtil;
import com.javasm.qingqing.common.utils.MD5Util;
import com.javasm.qingqing.login.service.LoginService;
import com.javasm.qingqing.webuser.entity.WebUser;
import com.javasm.qingqing.webuser.entity.WebUserInfo;
import com.javasm.qingqing.webuser.entity.WebUserPossession;
import com.javasm.qingqing.webuser.service.WebUserInfoService;
import com.javasm.qingqing.webuser.service.WebUserService;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
 * @className: LoginServiceImpl
 * @author: gfs
 * @date: 2025/11/29 11:08
 * @version: 0.1
 * @since: jdk17
 * @description:
 */
@Service
public class LoginServiceImpl implements LoginService {

    @Resource
    WebUserService webUserService;
    @Resource
    WebUserInfoService webUserInfoService;
    @Resource
    RedisTemplate<String,WebUser> redisTemplate;
    @Resource
    IpAddressUtil ipAddressUtil;

    @Override
    @WebLoginLog
    public WebUser doUnameLogin(String username, String password) {
        //先从Redis中获取用户信息 weuser:username:%s
        //考虑到Key会反复使用,新建一个常量类,统一管理Redis的Key
        String key = String.format(RedisKeys.WebUserName,username);
        //从Redis中获取数据
        WebUser webUser = redisTemplate.opsForValue().get(key);
        if (webUser != null){
            if (webUser.getUid() == null){
                throw new JavasmException(ExceptionEnum.User_Not_Found);
            }
            //说明已经查询到了数据
            //检查密码
            //如果密码不正确,抛出异常
            webUser.checkPassword(password);
            //刷新一下Redis的过期时间
            redisTemplate.expire(key,15, TimeUnit.DAYS);
            return webUser;
        }
        //Redis中获取不到数据了
        //根据用户名 查询用户信息
        WebUser loginUser = webUserService.getByUname(username);
        //如果用户信息是null ,说明数据库没有
        if (loginUser == null){
            redisTemplate.opsForValue().set(key,new WebUser(),10,TimeUnit.MINUTES);
            throw new JavasmException(ExceptionEnum.User_Not_Found);
        }
        //如果有用户信息,校验密码是否正确
        loginUser.checkPassword(password);
        //登录成功之后,存入Redis之前,要查询出WebUserInfo的信息
        WebUserInfo userInfo = webUserInfoService.getById(loginUser.getUid());
        if (userInfo != null){
            loginUser.setUserInfo(userInfo);
        }
        //登录成功了
        redisTemplate.opsForValue().set(key,loginUser,15,TimeUnit.DAYS);

        //返回用户信息
        return loginUser;
    }

    @Override
    @Transactional
    @WebLoginLog//注册完成,自动登录,记录登录日志
    public WebUser doRegister(WebUser webUser) {
        //用户数据 单独拿出来
        String username = webUser.getUsername();
        String password = webUser.getPassword();
        String phone = webUser.getPhone();
        String nickname = webUser.getUserInfo().getNickname();
        //校验 用户名是否重复
        WebUser byUname = webUserService.getByUname(username);
        if (byUname != null){
            throw new JavasmException(ExceptionEnum.User_Exist);
        }
        //密码,如果密码加密,需要把用户传入的密码加密之后,重新放到对象中
        String md5password = MD5Util.encrypt(password);
        webUser.setPassword(md5password);
        //注册时间
        webUser.setRegTime(new Date());
        //状态--数据库中已经配置了默认值,但是等会要把数据存入Redis,不想二次查询数据库,所以配一个默认值
        webUser.setStatus(0);
        //保存webUser数据
        webUserService.save(webUser);
        //数据存储成功,就会有uid
        Integer uid = webUser.getUid();
        if (uid == null){
            throw new JavasmException(ExceptionEnum.SYSTEM_ERROR);
        }
        WebUserInfo userInfo = webUser.getUserInfo();
        userInfo.setUid(uid);
        //如果昵称是空的,生成一个
        if (StringUtils.isEmpty(nickname)){
            //用户没有传昵称,分配一个默认昵称
            userInfo.setNickname("虾仁"+uid);
        }
        //分配头像
        String path = "http://cd.ray-live.cn/imgs/headpic/pic_%s.jpg";
        //顾前不顾后
        int num = ThreadLocalRandom.current().nextInt(1, 580);
        userInfo.setHeadPic(String.format(path,num));
        //ip
        userInfo.setIpAddress(ipAddressUtil.getClientIp());
        //其他的属性
        userInfo.setVipGrade(1);
        userInfo.setFansNum(0);
        userInfo.setLoadGrade(1);
        userInfo.setGodStatus(0);
        userInfo.setLockoutStatus("1");
        //...
        userInfo.insert();
        //添加用户财富信息
        WebUserPossession possession = new WebUserPossession();
        possession.setUid(uid);
        possession.setRichNum(0L);
        possession.setCharmNum(0L);
        possession.insert();
        webUser.setPossession(possession);
        //数据保存到redis
        new Thread(()->{
            String uname_key = String.format(RedisKeys.WebUserName,username);
            redisTemplate.opsForValue().set(uname_key,webUser,15,TimeUnit.DAYS);
            String uid_key = String.format(RedisKeys.WebUserId,uid);
            redisTemplate.opsForValue().set(uid_key,webUser,15,TimeUnit.DAYS);
        }).start();
        return webUser;
    }
}
  • 修改了密码加密策略,所以登录模块的密码校验更改了
复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WebUser extends Model<WebUser> {

    @TableId(type = IdType.AUTO)
    private Integer uid;
    //用户名
    private String username;
    //密码
    private String password;
    //0正常1删除2封号
    private Integer status;
    //邮箱
    private String email;
    //手机号
    private String phone;
    //注册时间
    private Date regTime;

    @TableField(exist = false)
    private WebUserInfo userInfo;

    @TableField(exist = false)
    private WebUserPossession possession;

    public void checkPassword(String pwd){
        if (!MD5Util.encrypt(pwd).equals(password)){
            throw new JavasmException(ExceptionEnum.Password_Error);
        }
    }

}
  • 注册的时候,有多表添加,增加了事务
复制代码
@Configuration
@EnableTransactionManagement
public class ServerConfig {
}

2、页面

复制代码
<template>
  <div class="reg-main">
    <div class="reg-head">
      <div class="row video-main">
        <div class="col-lg-12 col-md-12">
          <video width="100%"
                 class="head-video"
                 src="http://cd.ray-live.cn/video/goddess-moon.mp4"
                 poster="http://cd.ray-live.cn/video/goddess-moon.jpg"
                 autoplay
                 loop
                 muted></video>
        </div>
      </div>
    </div>
    <div class="container">
      <div class="row">
        <div class="col-lg-12 col-md-12">
          <div class="register-form">
            <h2>注册</h2>
            <form id="reg-form" class="needs-validation" novalidate ref="form">
              <div class="form-group">
                <label>用户名</label>
                <input type="text" class="form-control" placeholder="请输入用户名"
                       v-model="user.username" required minlength="4">
                <div class="invalid-feedback" >用户名长度不足</div>
                <div class="valid-feedback" >用户名不能为空</div>
              </div>
              <div class="form-group">
                <label>密码</label>
                <input type="password" class="form-control" placeholder="请输入密码"
                       v-model="user.password" required>
                <div class="invalid-feedback">密码不能为空</div>
              </div>
              <div class="form-group">
                <label>手机号</label>
                <input type="number" class="form-control" placeholder="请输入手机号"
                       v-model="user.phone" required minlength="11">
                <div class="invalid-feedback">请输入11位手机号</div>
              </div>
              <div class="form-group">
                <label>昵称</label>
                <input type="text" class="form-control" placeholder="请输入昵称"
                       v-model="user.userInfo.nickname" required>
                <div class="invalid-feedback">昵称不能为空</div>
              </div>
              <button type="button" class="default-btn" @click="doRegister">注册</button>
              <div class="or-text"><span></span></div>
              <button type="button" class="google-btn" @click="jumpLogin">返回登录</button>
            </form>
          </div>
        </div>
      </div>
    </div>

    <div class="row reg-logo">
      <div class="col-lg-12 col-md-12">
        <img src="@/assets/imgs/logo_reg.png" alt="image">
      </div>
    </div>
  </div>
</template>

<script setup>
import {ref, reactive, getCurrentInstance, onMounted} from "vue";
import router from '@/router/index'
import api from '@/utils/request.js'
import userStore from "@/stores/UserStore.js";
import {ElMessage} from "element-plus";


let user = ref({
  "username": "",
  "password": "",
  "userInfo": {
    "nickname": "",
    "email": ""
  }

})
let form = ref()
let jumpLogin = () => {
  router.push("/login")
}
//登录
let doRegister = () => {
  //$('#already').modal('show')
  if (form.value.checkValidity()){
    //校验通过
    api.postJson("/login/doReg",user.value).then(result=>{
      if (result.code === 200){
        userStore().userModel = result.data;
        ElMessage.success("注册成功");
        router.push("/")
      }else {
        ElMessage.error(result.msg)
      }

    })
  }
  form.value.classList.add('was-validated');
}
onMounted(() => {

})

</script>

<style scoped>
.reg-head {
  height: 480px;
  display: flex;
  justify-content: center;
  position: absolute;
  width: 99.3%;
  top: 0px;
  left: 0px;
  z-index: -1;
}

.login-head video {
  position: absolute;
  top: 0;
  left: 0;
  min-width: 100%; /* 保证视频宽度至少为 100% */
  min-height: 100%; /* 保证视频高度至少为 100% */
  width: auto;
  height: auto;
  z-index: -100;
  object-fit: cover;
}
.reg-main{
  height: 100%;
  width: 99.9%;
  max-width: 1920px;
  min-height: 99vh;
  min-width: 1320px;
  max-height: 100%;
  position: absolute;
  z-index: -2;
  scrollbar-width: none;
  overflow: hidden;
}
.register-form {
  background: rgba(255, 255, 255, 0.5);
}

.container {
  right: 50px;
  top: 50px;
  position: absolute;
  width: 660px;
}

.reg-logo {
  position: absolute;
  left: 100px;
  top: 0px
}

</style>

六、首页推荐

1、游戏列表

找到游戏列表,game表,生成代码,写游戏列表接口,获取热门list

  • Controller
复制代码
@RestController
@RequestMapping("/home")
public class HomeController {
    @Resource
    HomeService homeService;

    @GetMapping("/hot/game/list")
    public R queryHotGameList(){
        List<Game> gameList = homeService.queryHotGameList();
        return R.ok(gameList);
    }
}
  • Service
复制代码
@Service
public class HomeServiceImpl implements HomeService {

    @Resource
    GameService gameService;

    @Override
    public List<Game> queryHotGameList() {
        List<Game>  gameList = gameService.getHotGameList();
        return gameList;
    }
}
  • gameService
复制代码
@Service("gameService")
public class GameServiceImpl extends ServiceImpl<GameDao, Game> implements GameService {

    @Override
    public List<Game> getHotGameList() {
        LambdaQueryWrapper<Game> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Game::getIshot,1);
        queryWrapper.orderByAsc(Game::getSort);
        List<Game> list = list(queryWrapper);
        return list;
    }
}

2、热门游戏人物列表

  • Controller
复制代码
    @GetMapping("/hot/game/user/list/{id}")
    public R queryGameUserListByGid(@PathVariable Integer id){
        ParameterUtils.checkParameter(id);
        List<HomeGameVo> list = homeService.queryHotGameUserList(id);
        return R.ok(list);
    }
  • HomeService
复制代码
    @Override
    public List<HomeGameVo> queryHotGameUserList(Integer id) {
        List<HomeGameVo> list;
        if (id == -1){
            list = gameService.queryHomeGameUserHot();
        }else {
            //根据游戏id 查询信息
            list = gameService.queryHomeGameUserList(id);
        }
        return list;
    }
  • GameService
复制代码
    @Override
    public List<HomeGameVo> queryHomeGameUserList(Integer id) {
        return gameDao.selectGameUserListByGid(id);
    }

    @Override
    public List<HomeGameVo> queryHomeGameUserHot() {

        return gameDao.selectGameUserHotList();
    }
  • GameDao
复制代码
public interface GameDao extends BaseMapper<Game> {

    @Select("SELECT skill.skill_price as price," +
            "skill.uid,web.head_pic," +
            "web.nickname,game.`name` as game_name " +
            "FROM skill,game,web_user_info web " +
            "WHERE skill.game_id = game.id " +
            "AND skill.uid = web.uid " +
            "AND skill.game_id = #{id} limit 12")
    List<HomeGameVo> selectGameUserListByGid(Integer id);

    @Select("SELECT skill.skill_price as price," +
            "skill.uid,web.head_pic," +
            "web.nickname,game.`name` as game_name " +
            "FROM skill,game,web_user_info web " +
            "WHERE skill.game_id = game.id " +
            "AND skill.uid = web.uid " +
            "AND skill.skill_promotion > 0  " +
            "ORDER BY skill.order_times DESC," +
            "skill.skill_price ASC " +
            "LIMIT 12")
    List<HomeGameVo> selectGameUserHotList();

}

3、优化

减少查询的次数,减少数据库访问的次数,减少Redis的访问次数,减少对业务服务器的访问次数
思路

  • 不能让用户每次访问首页的时候,都去查询数据库

  • 让用户去查询Redis缓存

  • 客户端和服务端通信了几次

理想状态

用户第一次访问首页,通过1个接口,把所有的数据,都得到。

所有的数据:推荐的游戏列表,把每个游戏推荐的列表,也一起发送过去

前端:把这些数据,存储到SessionStorage中

用户第二次访问首页的时候,直接访问本地的数据,不再请求Java服务器

  • HomeController
复制代码
    @GetMapping("/game")
    public R queryHomeGame(){
        List<Game> list = homeService.getAllGameList();
        return R.ok(list);
    }
  • HomeService
复制代码
    @Resource
    RedisTemplate<String,Object> redisTemplate;

    @Override
    public List<Game> getAllGameList() {
        //去Redis中查询数据
        String key = RedisKeys.HomeHotList;
        //数据是list ,但是不想存储成redislist类型,整存整取
        Object o = redisTemplate.opsForValue().get(key);
        if(o != null){
            return (List<Game>) o;
        }
        //说明缓存没有数据,数据库查询
        //查询游戏以列表
        List<Game> list = gameService.getHotGameList();
        //根据游戏列表,把所有的游戏 用户信息 查询到
        List<HomeGameVo> homeGameVoList = gameService.getHotGameUserList(list);
        //手工创建一个 id = -1的推荐游戏
        Game gameHot = new Game();
        gameHot.setId(-1);
        gameHot.setName("人气推荐");
        gameHot.setIcon("http://cd.ray-live.cn/imgs/game/jx.png");
        List<HomeGameVo> gameVoList = gameService.queryHomeGameUserHot();
        gameHot.setGameSkillList(gameVoList);
        list.add(0,gameHot);
        //循环游戏列表,寻找对应的陪玩人员信息
        list.forEach(game->{
            if (game.getId() != -1){
                //空的集合,等会存储当前循环的游戏,都有哪些人员
                List<HomeGameVo> gameSkillList = new ArrayList<>();
                homeGameVoList.forEach(homeGameVo -> {
                    if (game.getId().equals(homeGameVo.getGameId())){
                        //在大的集合中,找到当前游戏的陪玩人员信息,收集起来
                        gameSkillList.add(homeGameVo);
                    }
                });
                //陪玩列表,存入游戏对象
                game.setGameSkillList(gameSkillList);
            }
        });
        //数据组装完成,存入Redis
        redisTemplate.opsForValue().set(key,list,2, TimeUnit.HOURS);
        return list;
    }
  • GameService
复制代码
    @Override
    public List<HomeGameVo> getHotGameUserList(List<Game> list) {

        //"1,2,3,4"
        String ids = list.stream().map(game -> String.valueOf(game.getId())).collect(Collectors.joining(","));
        //减少数据库访问次数,不能使用for循环访问
        List<HomeGameVo>  gameVoList = gameDao.selectGameUserListByGids(ids);
        return gameVoList;
    }
  • GameDao
复制代码
    @Select("SELECT skill.skill_price as price," +
            "skill.uid,web.head_pic," +
            "web.nickname,game.`name` as game_name, game.id as game_id " +
            "FROM skill,game,web_user_info web " +
            "WHERE skill.game_id = game.id " +
            "AND skill.uid = web.uid " +
            "AND skill.game_id in (${ids}) limit 10")
    List<HomeGameVo> selectGameUserListByGids(String ids);
  • HomeGameVo
复制代码
@Data
public class HomeGameVo {
    private Integer uid;
    private String headPic;
    private String nickname;
    private String gameName;
    private String price;
    private Integer gameId;
}

4 页面

4.1 Pina
复制代码
import {ref, reactive, getCurrentInstance, onMounted} from "vue";
import {defineStore} from "pinia";
​
export default defineStore('userHomeGameStore', () => {
        const gameList = ref([])
        return {
            gameList
        }
    }, {
        persist: {
            storage: sessionStorage,
            paths: ['gameList']
        }
    }
)
4.2 vue
复制代码
<template>
  <div class="hot-room">
    <div class="row">
      <h1>人气推荐</h1>
    </div>
    <div class="row">
      <div v-for="game in game_list"
           class="col-md-2 col-lg-1 card a game-card"
           :id="'game-card-'+game.id" @click="viewUser(game.id)">
        <img :src="game.icon">
        <div class="card-body">
          <h6 class="card-title">{{game.name}}</h6>
        </div>
      </div>
    </div>
    <div class="row skill-list">
      <div v-for="user in game_user_list" class="col-lg-2 col-md-4 card skill-card" >
        <img :src="user.headPic">
        <div class="card-body">
          <h5 class="card-title">{{user.nickname}}</h5>
        </div>
        <div class="card-body row">
          <div class="col">
            <span class="bg-success text-white p-1">{{user.gameName}}</span>
          </div>
          <div class="col">
            <span class="text-danger a">{{user.price}}/小时</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
​
<script setup>
import {ref, onMounted, nextTick} from "vue";
import router from '@/router/index'
import api from '@/utils/request.js'
import UserHomeGameStore from "@/stores/UserHomeGameStore.js";
​
let game_list = ref([])
let game_user_list=ref([])
​
let viewUser = (gameId) => {
  //移除game-card的class属性 active
  document.querySelectorAll(".game-card").forEach(item => {
    item.classList.remove("active")
  })
  //添加当前game-card的class属性 active
  document.querySelector("#game-card-" + gameId).classList.add("active")
  game_user_list.value = game_map.value.get(gameId);
}
​
let query=()=>{
  //先去SessionStorage中查询数据
  if (UserHomeGameStore().gameList.length > 0){
    //本地有数据,已经缓存了
    game_list.value = UserHomeGameStore().gameList;
    createMap(UserHomeGameStore().gameList)
    nextTick(()=>{
      viewUser(-1)
    })
  }else {
    api.get("/home/game").then(result=>{
      //本页循环需要使用的数据
      game_list.value = result.data;
      //本地SessionStorage缓存数据
      UserHomeGameStore().gameList = result.data;
      createMap(result.data)
      nextTick(()=>{
        viewUser(-1)
      })
    })
  }
​
}
let game_map = ref({})
function createMap(list){
  let map = new Map();
  list.forEach(game=>{
    map.set(game.id,game.gameSkillList);
  })
  game_map.value = map;
}
​
onMounted(() => {
  query();
});
</script>
​
<style scoped>
​
</style>
相关推荐
w***48144 分钟前
Maven Spring框架依赖包
java·spring·maven
汤姆yu1 小时前
基于springboot的乡村信息建设管理系统
java·spring boot·后端
Halo_tjn1 小时前
Java List集合
java·windows·计算机
多敲代码防脱发1 小时前
初识Spring-Cloud——集群与分布式
java·spring boot·spring
O***Z6161 小时前
HeidiSQL导入与导出数据
java
毕设源码-朱学姐1 小时前
【开题答辩全过程】以 老年公寓信息管理系统为例,包含答辩的问题和答案
java·spring boot
GoodStudyAndDayDayUp1 小时前
WIN11安装配置验证java\maven
java·开发语言·maven
一 乐1 小时前
游戏账号交易|基于Springboot+vue的游戏账号交易系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·游戏
程序员-周李斌1 小时前
LinkedList 源码深度分析(基于 JDK 8)
java·开发语言·数据结构·list