SpringBoot-Vue+Mybatis整合后台项目

京淘后台整合项目

一级目录

1.开发前准备

1.2 vue-cli脚手架

安装nodejs并验证

安装包

安装nodejs,下一步下一步就可以,可以安装最新的15版本,win7的话可以安装14版本.使用以下dos命令提示符下执行:

node -v     # v8.11.3,至少8以上,最新的是v15.11.0

配置npm

Nodejs下的包管理器,Nodejs中包含了npm,无需单独安装.默认去官网下载资源,可以换成国内的镜像

npm config get registry # 查看当前配置的镜像,结果是默认的国外网址https://registry.npmjs.org/

npm config set registry https://registry.npm.taobao.org #设置成淘宝镜像

npm config get registry #再获取查看,结果是修改后的https://registry.npm.taobao.org/ 

参数说明
-i 安装指令,全拼: install
-S 生产环境,全拼: --save
-D 开发环境,全拼: --save—dev
-O 可选依赖,全拼: --save—optional
-E 精确安装指定模块版本,全称:--save—exact
-g 全局安装,全拼: --global

脚手架安装

vue-cli: 用户生成Vue工程模板(帮你快速构建一个vue的项目,也就是给你一套vue的结构,包含基础的依赖库)
vue-cli: 脚手架工具安装与配置(需要几分钟)

npm install vue-cli -g #安装vue-cli脚手架---可能比较慢,要等几分钟

npm uninstall vue-cli -g #卸载vue-cli脚手架 --- 大可不必

vue –V #查看版本

where vue #vue安装在哪里

工作空间

进入工作空间目录:D:\webpase-vue

生成vue项目

基于vue.js的官方webpack模板:(乱码无需理会)
webpack: 它主要的用途是通过CommonJS的语法把所有浏览器端需要发布的静态资源做相应的准备,比如资源的合并和打包

vue init webpack jt01 #此处项目名不能使用大写---可能比较慢,要等

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注:

jt01 为自定义的 项目名称
产生项目webpack模板,目录100+m,可见内容多复杂,庞大
会自动生成node_modules临时目录,可以删除,每次编译、运行会自动产生

启动项目 & 停止项目
cd jt01 # 进入项目目录

npm run dev # 自动启动服务,ctrl+c 停止,可能要等几分钟

测试访问

注意:端口号可能不同,默认为8080,如果发现端口占用npm很聪明,它会自动改变端口号,以其具体提示的端口信息为准。
在这里插入图片描述

使用idea管理Vue项目

引用图片

引用element框架组件

element官网地址:
饿了么官网地址https://element.eleme.cn/#/zh-CN/component/installation
在这里插入图片描述

在这里插入图片描述

下载依赖:

npm i element-ui -S

在这里插入图片描述

引用依赖

在这里插入图片描述

搭建element主题框架

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
去掉不相关展示
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

HBuilderX管理Vue项目

HBuilderX是最新前端开发利器之一,全面支持Vue的开发,具有丰富的提示,使用它打开:D:\webpase-vue\jt01目录(可自行定义自己的目录)
在这里插入图片描述
在这里插入图片描述

1.3 VUE项目结构

在这里插入图片描述

目录结构

这个目录结构非常重要,大家要熟记。就如你要生孩子去妇幼保健院,你要游泳去游泳馆。规定好每个目录的作用,方式和位置就约定统一了。有了这套规则,文件就不会乱放,这样找资源时就知道到哪里找,写代码文件时就按功能放到指定的目录中。这种方式已经非常常见,如Maven构建项目目录有强制的约定,如Spring框架中约定大于配置。
view 就是用户要访问的页面都存放在这个目录下,如Index.vue
static中保存一些静态的资源,如jquery、css、图片等
src 目录是一个很大的目录,包罗万象
components 把项目中所需要的组件都放在此目录下,默认会创建一个HelloWorld.vue,我们可以自己添加,如添加Header.vue
router 访问的路径,Vue提倡的是SPA(Single Page WebApplication)单页面设计,这是以前旧页面中如果包含其他资源,必然涉及到路径问题。Html没有很好的解决这个问题,而router它是一种解决路径的新发明,很好的解决了多模块页面刷新问题。简而言之就是给组件指明一个路径,我们怎么去访问它。不同组件是依靠配置的路径router来访问的。Router非常强大,老系统是url改变是在服务端进行刷新,而router支持在客户端刷新,就是url变化,页面内容变化,但不请求服务器端,以前的程序是做不到的。此处不好理解,后期专门章节论述,不必心急,先记录下这个特点
在这里插入图片描述

重要文件说明

Vue项目这么多文件,它们什么关系?写代码该从哪里下手呢?
常见操作: 1, 在components里写自定义组件 2, 在App.vue里注册自定义组件 3, 在main.js里引入第三方js

 index.html 首页,Vue是SPA单页面开发,页面入口,定义了一个div,并设置id=app
 src/main.js 公共的组件就直接配置到这个文件中,私有的就配置到view中
在这里插入图片描述
 src/App.vue 根组件,可以添加自定义组件

 src/router/index.js 引入子组件HellWorld.vue
在这里插入图片描述

简单来说项目加载的过程是:
index.html->main.js->App.vue->index.js->HelloWorld.vue

可以看到Vue项目是自有一套规则,一套机制的,非常系统化的。有固定的文件,有自定义的文件,各自放在指定的目录下,指定的文件中,指定的地方,最终来实现用户的需求。那在开发之前,你就必须了解这套机制,写代码的时候就规则清晰,如有神助,知道该如果写代码,知道为什么这么写,代码文件该放在哪,它们是谁调用谁,互相怎么调用的了。

为什么要花这么大篇幅讲这几个文件呢

很多同学写代码时,听老师讲没问题,可自己做就乱了方寸,脑袋空白甚至都是浆糊,不知道代码该写在哪里?不知道出错了该如何下手,这是为何呢?老师都教了啊?就是自己没有去把所学的知识系统的、有效的组织起来,内容还只是一个一个点,无法把这些点很好的连接起来形成线,先在形成面,面在形成体。点线面体真正组织起来,才会逐渐清晰代码的整体过程。

那如何做到呢?

思考,但复杂的事务不是马上脑袋就能跟上的,得晕好久呢。那怎么办,真正的绝招,多敲多练,反复练习中慢慢总结出其真正的规律。就像我们打游戏,游戏高手,不是天生,是反复练习,反复失败,失败就是成功之母,这句话是真的。

自定义组件

概述
组件(Component)是 Vue.js 最强大的功能之一。
组件可以扩展 HTML 元素,封装可重用的代码。
组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树
在这里插入图片描述

创建组件Car.vue
<template>
  <!-- 获取值 -->
  <h1>{{msg}}</h1>
  <!--  <h1>{{msg}}</h1> 报错,只能有一个根标签 -->
</template>

<script>
  /* 自定义组件 */
  export default{
    name:'Car',
    data(){
      return{
        msg:'hi components'
      }
    }
  }
</script>

<style>
</style>



修改App.vue,注册自定义组件
<template>
  <div id="app">
      <!-- 3.使用自定义组件,就像用标签 -->
    <Car></Car>
  </div>
</template>

<script>
  // 1.导入自定义组件,必须写./
  import Car from './components/Car.vue'

  export default {
    name: 'App',
    components:{ // 2.添加自定义组件
      Car //用了第一步的名字
    }
  }
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>



测试

在这里插入图片描述
在这里插入图片描述

安装 element-ui 插件

访问官网: https://element.eleme.cn/#/zh-CN/component/installation,查看组件指南

安装

在工程目录下,使用 npm 的方式安装,它能更好地和 webpack 打包工具配合使用。

npm i element-ui –D  # 下载资料,这可能要等几分钟

在这里插入图片描述

修改 main.js,引入Element

你可以引入整个 Element,或是根据需要仅引入部分组件。我们先介绍如何引入完整的 Element。
你可以参考官网的【快速上手】

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false

import ElementUI from 'element-ui'; //导入element
import 'element-ui/lib/theme-chalk/index.css';//导入element的css
//以上代码便完成了 Element 的引入。需要注意的是,样式文件需要单独引入。
Vue.use(ElementUI);//使用element


/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})


修改 Car.vue

至此,一个基于 Vue 和 Element 的开发环境已经搭建完毕,现在就可以使用Element代码了。

<template>
  <!-- 获取值 -->
  <div>
   <i class="el-icon-edit"></i>
   <i class="el-icon-share"></i>
   <i class="el-icon-delete"></i>
   <el-button type="primary" icon="el-icon-search">搜索</el-button>
    {{msg}}
  </div>
<!--  <h1>{{msg}}</h1> 报错,只能有一个根标签 -->


</template>

<script>
  // 定义导出的组件
  export default{
    name:'Item',
    data(){
      return{
        msg:'京淘电商管理系统'
      }
    }
  }
</script>

<style>
</style>


1.4 安装vue的客户端

 npm install -g @vue/cli --force

在这里插入图片描述

选择VUE 创建项目

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 后端项目搭建

在这里插入图片描述

编辑pom.xml文件

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--引入插件lombok 自动的set/get/构造方法插件  -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--mybatis依赖包-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <!--jdbc依赖包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

编辑层级代码

在这里插入图片描述

关于脚手架的说明

在这里插入图片描述

关于main.js的说明

概念: VUE 引入组件的概念
组件好处: 封装CSS样式/封装JS样式/HTML代码片段. xxxx.vue命名.
父子组件参数传递: 需要使用 Vue.prototype.h t t p = a x i o s 以 后 使 用 http = axios 以后使用http=axios以后使用http.xxx 发起Ajax请求.
在这里插入图片描述

关于路由说明

const routes = [
  {path: '/', redirect: '/login'},
  {path: '/login', component: Login},
  {path: '/elementUI', component: ElementUI}
]


3.用户登录业务实现

页面JS分析

在这里插入图片描述

用户登录JS

在这里插入图片描述

用户业务接口文档说明

请求路径: /user/login
请求方式: POST
请求参数
参数名称 参数说明 备注
username 用户名 不能为空
password 密码 不能为空
响应数据 SysResult对象
参数名称 参数说明 备注
status 状态信息 200表示服务器请求成功 201表示服务器异常
msg 服务器返回的提示信息 可以为null
data 服务器返回的业务数据 返回密钥token信息
在这里插入图片描述
返回值格式如下:


	{"status":200,"msg":"服务器调用成功!","data":"1e893a97634847b3a8b499b173bea620"}
	

编辑SysResult对象

在这里插入图片描述

用户登录模块实现

登录业务逻辑

登录前端表单传两个字符串,username和password给后端,由于是用vue的axios来传,一个json,需要用注解@RequesttBoody把json转成对象在拿对象来接传一个对象去查,然后在调用service层方法,在调用mapper里的方法查询数据在返回。
**注意:**用户的密码都用了MD5加密,需要将传来的密码装成MD5加密,然后在封装到对象,然后传一个对象到数据库里面进行查询。做一个if判断,如果能查到有数据,返回200,json给前端显示。

查询用户信息的mapper和xml方法

在这里插入图片描述
在这里插入图片描述

关于秘钥说明

说明: 当用户登录之后,可以跳转到系统的首页. 到了系统首页之后,用户可以进行其它的业务操作. 系统如何判断正在操作业务的用户 已经登录?
业务说明:
一般在登录认证系统中,都会返回秘钥信息.来作为用户登录的凭证.
秘钥特点: 最好独一无二.

动态生成秘钥: UUID

登录实现的sevice层的方法与方法实现类实现

由于密钥是用户登录凭证,所以返回值需要是token密钥字符串。

在这里插入图片描述
在这里插入图片描述

登录的UserController
/**
     *  URL地址: /user/login
     *  请求类型: post
     *  参数:    JSON串 username/password
     *  返回值:  SysResult对象
     */
    @PostMapping("/login")
    public SysResult login(@RequestBody User user){
        String token = userService.findUser(user);
        if(token==null){
            return SysResult.fail();//201
        }
        return SysResult.success(token);//200
    }

关于Session和Cookie说明

业务需求说明

用户的请求是一次请求,一次响应. 当响应结束时,服务器返回的数据 也会销毁. 问题: 如果销毁了token 则认为用户没有登录.需要重复登录.
如何解决该问题: 应该持久化token信息.

Session

Session:在计算机中,尤其是在网络应用中,称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在Session对象中。有关使用Session 对象的详细信息,请参阅“ASP应用程序”部分的“管理会话”。注意会话状态仅在支持cookie的浏览器中保留。
特点: Session总结
Session 称之为 会话控制 技术
Session生命周期, 会话结束 对象销毁.
Session的数据存储在内存中.
Session只可以临时存储数据.不能永久存储.

cookie总结

Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息 [1] 。

特点:

  1. 类型: 小型文本文件.
  2. 文件通常是加密的.
  3. cookie 可以临时或者永久存储.

关于Cookie和Session说明

手机银行的登录信息? Session存储. 数据安全性高
腾讯视频会员登录信息? Cookie存储 1个月免密登录.
公司的财务系统登录信息? Session存储
购物系统的登录信息? Cookie存储.

系统首页跳转 —路由拦截器

说明: 当用户在没有登录的条件下. 用户可以手动输入请求地址. 可以直接跳转项目. 这样的方式非常不安全.
解决方案: 前端通过拦截器 控制用户是否登录.
拦截器说明: 用户拦截的是URL中跳转的路径.
结果: 1.拦截 跳转到登录页面.
2.放行 跳转用户目标页面.
当从登录时首先进入route下的index.js。

/**
 *  参数说明:
 *    1.to 到哪里去
 *    2.from 从哪里来
 *    3.next 请求放行
 *  拦截器策略:
 *    1.如果用户访问/login登录页面 直接放行
 *    2.如果访问其它页面,则校验是否有token
 *      有token     放行
 *      没有token   跳转到登录页面
 */
router.beforeEach((to,from,next) => {
  if(to.path === '/login') return next()
  //获取token数据信息
  let token = window.sessionStorage.getItem('token')
  if(token === null || token === ''){
     return next("/login")
  }
  //放行请求
  next()
})

在这里插入图片描述

4 左侧菜单展现

后端管理系统后显示
创建左侧列表的实体类

@Data
@Accessors(chain = true)
public class Rights extends BasePojo{
    private Integer id;
    private String name;
    private Integer parentId;
    private String path;
    private Integer level;
    private List<Rights> children; //不是表格固有属性
}

前端JS说明

生命周期函数调用JS函数

  created() {
      //动态获取左侧菜单信息
      this.getMenuList()
      //设定模式选中按钮
      this.defaultActive = window.sessionStorage.getItem("activeMenu")
    },


发起Ajax请求获取服务器数据

 async getMenuList() {
       const {data: result} =  await this.$http.get('/rights/getRightsList')
       if(result.status !== 200) return this.$message.error("左侧菜单查询失败")
       this.menuList = result.data
      },

展示左侧列表接口文档说明

请求路径 /rights/getRightsList
请求类型 GET
请求参数 无
响应数据 SysResult对象
参数名称 参数说明 备注
status 状态信息 200表示服务器请求成功 201表示服务器异常
msg 服务器返回的提示信息 可以为null
data 服务器返回的业务数据 返回权限List集合

父子关系封装/Sql语句写法

rights表的介绍 由于一张里包含了所有父类和子类或者子子类。

在这里插入图片描述
在这里插入图片描述

查询出所有父级和他的子级,用左表关联查。id=parent_id

由于父级和子级都是再一张表rights中,所有用两次采用关联取别名的方法。

SELECT * FROM 
rights p 
    LEFT JOIN
rights c ON
p.id=c.parent_id;
由于要避免null和*在查询中

优化一:

SELECT * FROM 
rights p 
    LEFT JOIN
rights c ON
p.id=c.parent_id
WHERE p.parent_id=0;

最终优化:

SELECT p.id,p.name,p.parent_id,p.path,p.level,p.created,p.updated ,
c.id c_id,c.name c_name,c.parent_id c_parent_id,c.level c_level ,c.created c_created,c.updated c_updated   FROM 
rights p 
    LEFT JOIN
rights c ON
p.id=c.parent_id
WHERE p.parent_id=0;

RightsMapper接口

public interface RightsMapper {

    public List<Rights> getRightsList();
}

编辑Rights映射文件

<mapper namespace="com.jt.mapper.RightsMapper">

    <select id="getRightsList" resultMap="rightsRM">
       select p.id,p.name,p.parent_id,p.path,p.level,p.created,p.updated,
       c.id c_id,c.name c_name,c.parent_id c_parent_id,c.path c_path,
       c.level c_level,c.created c_created,c.updated c_updated
	    from
            rights p
	    left join
            rights c
	    on
	        c.parent_id = p.id
        where p.parent_id = 0
    </select>

    <resultMap id="rightsRM" type="Rights" autoMapping="true">
        <id column="id" property="id"/>
        <!--一对一封装子级菜单List集合-->
        <collection property="children" ofType="Rights">
            <!--封装主键ID-->
            <id column="c_id" property="id"/>
            <result column="c_name" property="name"/>
            <result column="c_parent_id" property="parentId"/>
            <result column="c_path" property="path"/>
            <result column="c_level" property="level"/>
            <result column="c_created" property="created"/>
            <result column="c_updated" property="updated"/>
        </collection>
    </resultMap>
</mapper>

编辑Rights映射文件-子查询的方式

 <!--采用子查询方式-->
    <select id="getRightsList" resultMap="RightsRm">
        select * from rights where parent_id=0
    </select>
    <resultMap id="RightsRm" type="Rights" autoMapping="true">
        <id column="id" property="id"></id>
        <collection property="children" ofType="Rights" select="selectChildren" column="id"></collection>
    </resultMap>
    <select id="selectChildren" resultType="Rights">
        select * from rights where parent_id=#{id}
    </select>

Ajax 如何实现异步调用?

Ajax特点:

  1. 局部刷新
  2. 异步访问
    核心组件: Ajax 引擎!!
    在这里插入图片描述

vue知识点讲解

功能说明: 组件之间的嵌套问题.
定义路由步骤:

  1. 定义路由url地址.
  2. 路由填充位(占位符)
  3. 定义组件(了解)
  4. 定义路由策略
  5. 实现路由挂载

父子组件嵌套总结:
6. 定义父级组件
7. 在这里插入图片描述
路由策略:
在这里插入图片描述
如果需要嵌套 通过 router-view 进行占位, 通过children属性定义父子关系的结构. 当点击子组件时,会在父级组件的router-view中展现子组件.
首页嵌套规则
在Home组件中定义路由的占位符
在这里插入图片描述
定义父子组件的策略
在这里插入图片描述

5.用户列表显示-分页显示

用户列表查询分页业务接口说明

请求路径: /user/list
请求类型: GET
请求参数: 后台使用PageResult对象接收
请求案例: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10
参数名称 参数说明 备注信息
query 用户查询的数据 可以为null
pageNum 分页查询的页数 必须赋值不能为null
pageSize 分页查询的条数 必须赋值不能为null
响应参数: SysResult对象 需要携带分页对象 PageResult
参数名称 参数说明 备注信息
status 状态信息 200表示服务器请求成功 201表示服务器异常
msg 服务器返回的提示信息 可以为null
data 服务器返回的业务数据 返回值PageResult对象
PageResult 对象介绍
参数名称 参数类型 参数说明 备注信息
query String 用户查询的数据 可以为null
pageNum Integer 查询页数 不能为null
pageSize Integer 查询条数 不能为null
total Long 查询总记录数 不能为null
rows Object 分页查询的结果 不能为null

由于返回PageResult 需要传入pageNum和pageSize 去查分页的数据放入rows,在到数据库中查询user表中用户总数。放入PageResult 返回。
需要封装PageResult 对象
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class PageResult {
    private String query;       //查询数据
    private Integer pageNum;   //页数 ,总数量除以条数等于
    private Integer pageSize;  //条数,每页条数,每页几个用户
    private Long total;         //总数,用户总数
    private Object rows;        //查询的结果

}
需要封装UserMapper方法
 //用于查询user表中的用户总数(total),用户总数total/条数pageSize=总页数pageNum,无返回值
    @Select("select count(1)from user")
    Long getTotal();
    //根据传来的初始行start,和取的条数当作每页的条数size。由于前端需要返回totoal用户个数,
    // 和rows查询到数据(根据初始行和条数) 返回一个PageResult对象 limit 0,10 查询出0-9的数据放到rows中。
    //然后在用totoal/rows=总页
    @Select("select * from user limit #{start},#{size}")
    List<User> getUserListByage(@Param("start") int start, @Param("size") int size);
实现service的方法实现类
/**
 * 要求查询  1页10条
     * 特点: 数组的结果  口诀: 含头不含尾
     * 语  法:  select * from user limit 起始位置,查询的条数
     * 第一页:  select * from user limit 0,10       0-9
     * 第二页:  select * from user limit 10,10      10-19
     * 第三页:  select * from user limit 20,10      20-29
     * 第N页:   select * from user limit (n-1)*10,10
     *因为需要返回总数total和查询的条数封装到rows封装进pageResult,再传前端调用显示
     *需要封装start,size给mapper查询总条,放到rows进行再放到对象
     * @param pageResult
     * @return
     */
    @Override
    public PageResult getUserList(PageResult pageResult) {
        //因为需要返回总数total和查询的条数封装到rows封装进pageResult,再传前端调用显示
        long total=userMapper.getTotal();
        //需要封装start,size给mapper查询总条,放到rows进行再放到对象
        int size=pageResult.getPageSize();
        int start =(pageResult.getPageNum()-1)*size;
         List<User> rows = userMapper.getUserListByage(start, size);
        return pageResult.setTotal(total).setRows(rows);
    }
controller的实现类
/**
     * 业务说明:
     *  1. /user/list
     *  2.请求类型: GET
     *  3.参数接收: 后台使用PageResult对象接收
     *  3.返回值: SysResult<PageResult>
     */
     @GetMapping("/list")
     public SysResult getUserList(PageResult pageResult){//参数3
         //业务查询总数.分页条数.
         pageResult = userService.getUserList(pageResult);
        return SysResult.success(pageResult);//参数5个
     }

用户列表显示-分页显示-优化

用户状态的修改

请求路径 /user/status/{id}/{status}
请求类型 PUT
请求参数: 用户ID/状态值数据
参数名称 参数类型 参数说明 备注信息
id Integer 用户ID号 不能为null
status boolean 参数状态信息 不能为null
返回值结果: SysResult对象
{“status”:200,“msg”:“服务器调用成功!”,“data”:null}

编辑UserController

/**
     * 业务: 实现用户状态的修改
     * 参数: /user/status/{id}/{status}
     * 返回值: SysResult对象
     * 类型:   put 类型
     */
    @PutMapping("/status/{id}/{status}")
    public SysResult updateStatus(User user){

        userService.updateStatus(user);
        return SysResult.success();
    }

编辑UserService

 //更新操作时修改 status/updated 更新时间
    @Override
    public void updateStatus(User user) {
        user.setUpdated(new Date());
        userMapper.updateStatus(user);
    }

编辑UserMapper

	@Update("update user set status = #{status},updated = #{updated} where id=#{id}")
	void updateStatus(User user);

修改回显实现

业务逻辑

在这里插入图片描述

编辑UserController

 /**
     * 根据ID查询数据库
     * URL:/user/{id}
     * 参数: id
     * 返回值: SysResult(user对象)
     */
    @GetMapping("/{id}")
    public SysResult findUserById(@PathVariable Integer id){

        User user = userService.findUserById(id);
        return SysResult.success(user);
    }

编辑UserService

 @Override
    public User findUserById(Integer id) {

        return userMapper.findUserById(id);
    }

编辑UserMapper

	//原理: mybatis在进行单值传递时(int等基本类型/string) 取值时名称任意
    //     底层通过下标[0]获取的数据和名称无关.
    @Select("select * from user where id=#{id}")
    User findUserById(Integer id);

修改用户实现

修改的业务接口

请求路径: /user/updateUser
请求类型: PUT
请求参数: User对象结构
参数名称 参数说明 备注
ID 用户ID号 不能为null
phone 手机信息 不能为null
email 邮箱地址 不能为null
返回值: SysResult对象
参数名称 参数说明 备注
status 状态信息 200表示服务器请求成功 201表示服务器异常
msg 服务器返回的提示信息 可以为null
data 服务器返回的业务数据 null

/**
     * 业务说明: 实现数据的修改操作
     * URL:  /user/updateUser
     * 参数:  user对象
     * 返回值: SysResult对象
     * 请求类型: PUT
     */
    @PutMapping("/updateUser")
    public SysResult updateUser(@RequestBody User user){

        userService.updateUser(user);
        return SysResult.success();
    }

//id/phone/email
    @Override
    public void updateUser(User user) {

        userMapper.updateUser(user);
    }

 @Update("update user set phone=#{phone},email=#{email} where id=#{id}")
    void updateUser(User user);

用户新增操作

新增业务接口说明

请求路径 /user/addUser
请求类型 POST
请求参数: 整个form表单数据封装为js对象进行参数传递
参数名称 参数类型 参数说明 备注信息
username String 用户名 不能为null
password String 密码 不能为null
phone String 电话号码 不能为null
email String 密码 不能为null

编辑UserController

/**
     * 业务: 实现用户新增操作
     * url:  /user/addUser   post类型
     * 参数: 使用User对象接收
     * 返回值: SysResult对象
     */
    @PostMapping("/addUser")
    public SysResult addUser(@RequestBody User user){

        userService.addUser(user);
        return SysResult.success();
    }

编辑UserService

 /**
     * 1.密码进行加密
     * 2.添加状态码信息
     * 3.添加创建时间/修改时间
     * 4.完成入库操作 xml方式
     * @param user
     */
    @Override
    public void addUser(User user) {
        //1.密码加密处理
        Date date = new Date();
        String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
        user.setPassword(md5Pass)
                .setStatus(true)
                .setCreated(date)
                .setUpdated(date); //最好保证时间唯一性.
        userMapper.addUser(user);
    }

编辑UserMapper/xml映射文件

 	void addUser(User user);

 <!--完成用户新增操作-->
    <insert id="addUser">
        insert into user(id,username,password,phone,email,status,created,updated)
                value
                        (null,#{username},#{password},#{phone},#{email},#{status},#{created},#{updated})
    </insert>

作用域插槽

知识点
作用域插槽: 一般在表格数据展现时,可以动态获取当前行对象.
用法:
1.template
2.slot-scope属性=“变量”

 		   <el-table-column prop="status" label="状态">
              <!-- <template slot-scope="scope">
                  {{scope.row.status}}
              </template> -->
             <template slot-scope="scope">
                <el-switch v-model="scope.row.status" @change="updateStatus(scope.row)"
                  active-color="#13ce66" inactive-color="#ff4949">
                </el-switch>
             </template>
           </el-table-column>

页面JS分析

用户删除操作

在这里插入图片描述

 /**
     * 关于请求的小结
     *    1.常规请求方式 get/delete   ?key=value&key2=value2
     *    2.post/put    data: JS对象    后端接收@RequestBody
     *    3.restFul风格  /url/arg1/arg2/arg3   使用对象接收
     * 完成用户删除操作
     *  1.URL地址 /user/{id}
     *  2.参数:  id
     *  3.返回值: SysResult
     */
    @DeleteMapping("/{id}")
    public SysResult deleteUserById(@PathVariable Integer id){

        userService.deleteUserById(id);
        return SysResult.success();
    }

@Override
    public void deleteUserById(Integer id) {

        userMapper.deleteUserById(id);
    }

 	@Delete("delete from user where id=#{id}")
    void deleteUserById(Integer id);

Spring事务管理

事务特性–原一隔持四大特性

原子性: 原子不可分割.事务处理要么同时成功,要么同时失败.
一致性: 多线程条件下,数据应该保持一致.
隔离性: 多线程条件下,操作数据时,操作是独立互不干扰.
持久性: 将数据最终处理之后,持久化到数据库中.
谐音:日本名字 “原一隔持”

Spring中的事务

业务逻辑说明

说明: 下列代码运行时,会抛出异常.但是数据也会被删除.
结论: Spring不会默认添加事务控制.

 //面试题: 常见运行时异常   常见检查异常(编译异常)
    @Override
    public void deleteUserById(Integer id) {

        userMapper.deleteUserById(id);
        int a = 1/0;
    }

Spring控制事务

说明: Spring中提供了注解@Transactional 用来控制事务, 常见业务 增/删除/修改一般需要事务控制. 查询一般不用.

//面试题: 常见运行时异常   常见检查异常(编译异常)
    @Transactional      //事务的注解
    @Override
    public void deleteUserById(Integer id) {

        userMapper.deleteUserById(id);
        int a = 1/0;
    }

Spring控制事务策略

规则:

  1. Spring中的事务注解 默认条件下只处理运行时异常.如果遇到检查异常(编译异常)事务控制没有效果.
  2. 注解的属性控制
    rollbackFor = IOException.class , 遇到某种异常实现事务的回滚
    noRollbackFor = 遇到某种异常事务不回滚.
    readOnly = true 该操作是只读的,不能修改. 公告/合同等

Spring 全局异常处理机制

说明:

  1. 后端业务执行时,无法保证 业务运行不出错. 所以一般添加try-catch进行异常的捕获.
  2. 捕获异常是为了按照特定的要求 返回用户可以识别的有效数据.
  3. 如果添加了try-catch 则会对代码的可读性造成影响.
  4. 如果通过try-catch控制异常,则所有的controller方法都必须添加, 导致代码繁琐.
    在这里插入图片描述

全局异常处理机制

AOP 复习

说明: 面向切面编程
作用: 在代码结构中,实现了业务的松耦合.(解耦)
通知类型:

  1. before
  2. afterReturning
  3. afterTrowing
  4. after
  5. around 环绕通知 控制程序执行前后.

定义全局异常处理机制

编辑全局异常处理类

package com.jt.config;

import com.jt.vo.SysResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
//定义全局异常处理机制 内部封装了AOP
@RestControllerAdvice   //标识Controller层 返回值JSON串
public class SystemException {

    /**
     * 业务说明: 如果后端服务器报错,问应该返回什么数据?
     * 返回值:   SysResult对象(status=201)
     * 异常类型:  运行时异常
     */
    @ExceptionHandler(value = RuntimeException.class)
    public SysResult fail(Exception e){
        //输出异常
        e.printStackTrace();
        return SysResult.fail();
    }
}

Mybatis-Plus

MP介绍
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生
说明: 使用MP将不会影响mybatis的使用.
MP特性
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

Mybatis 特点

Mybaits 是一个半自动化的ORM映射框架
1.结果集可以实现自动化的映射. 自动
2.Sql语句需要自己手动完成. 手动
如果设计到单表的操作,如果每次都手写,则特别的啰嗦. 所以想办法优化.

		<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>

MP工作原理

实质: MP动态生成Sql语句.
铺垫:

  1. insert into 表名(字段名…) value (属性值…)

工作原理:

  1. 用户执行userMapper.insert(user); 操作
  2. 根据UserMapper的接口找到父级BaseMapper.根据BaseMapper的接口查找泛型对象User.
  3. 根据User.class 获取注解@TableName(“demo_user”),获取表名
  4. 根据User.class 获取所有的属性,根据属性获取指定的注解@TableField(value = “name”),获取字段名称
  5. 根据属性获取属性的值.之后动态拼接成Sql语句
  6. 将生成的Sql交给Mybatis执行入库操作.
  7. insert into demo_user(id,name,age,sex) value (null,xx,xx,xx)

MP使用特点: 根据其中不为null的属性进行业务操作!!!

将价格缩小两位数,保留两个小数

this.updateItemForm.price=(this.updateItemForm.price/100).toFixed(2)

增加商品之文件上传

service

public interface FileService {
    ImageVo fileUpload(MultipartFile file);
}

serviceImpl实现类

{
        //1.获取文件名。获取传上来的文件名。点击上传的。
        String fileName=file.getOriginalFilename();
        //1.1 先将文件的英文有大写的转成小写,再用正则表达式来判断是否是图片类型。
        fileName  = fileName.toLowerCase();
        //1.2 用正则来判断是否是图片类型
        if(!fileName.matches("^.+\\.(jpg||png||gif)$")){
            //如果不匹配,则返回空值终止服务
            return null;
        }
        //2.再次校验图片,判断是否为恶意程序,通过判断图片的特性来判断,通过判断图片的工具类ImageIO来。
        try {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            int height =bufferedImage.getHeight();//判断图片的宽高来判断
            int width=bufferedImage.getWidth();
            if(height==0||width==0){
                return null;
            }

            //3.上面的两步都通过的话就对图片进行存储,将图片到这路径下yyyy/MM/dd,
            //是根据时间日期动态转成路径的,
            SimpleDateFormat format = new SimpleDateFormat("/yyyy/MM/dd/");
            String dirDate=format.format(new Date());//将日期改为路径
            //4.创建imges图片的全路径。固定的localfile+动态生成的路径,避免写死。
            String fileDir=localFile+dirDate;
            //5.将全路径的图片进行疯装到file文件对象
           File fileObject=new File(fileDir);
           //6.判断是否存在文件架,如果没有创建一个
            if(!fileObject.exists()){
                fileObject.mkdirs();
            }
            //7.由于要防止文件重名需要用uuid,生成字符串当作文件
            String uuid=UUID.randomUUID().toString().replace("-", "");
            //8.获取文件类型名,后缀名,和uuid进行拼接。用字符串切割,拼接的方式获取,从做最后的小数点开始切割
            String fileType = fileName.substring(fileName.lastIndexOf("."));
            //9,通过两个拼接生成新的文件名
            String newFile=uuid+fileType;
            //10,实现文件上传功能,先获取文件的全路径=图片的存储路径+图片全名称
            String path=fileDir+newFile;
            //11.再将图片封装成对象进行上传
            file.transferTo(new File(path));
            //12.实现Image数据的返回,返回一个虚拟的路径,2021/11/15动态可变的。
            String virtualPath=dirDate+newFile;
            //13.准备要上传的图片url地址
            String url="https://img14.360buyimg.com/n0/jfs/t1/168265/1/15330/61508/6062fff2E07ff88e9/b3f0b2eadd310326.jpg";
            //返回ImageVo
            return new ImageVo(virtualPath,url,newFile);

        } catch (IOException e) {
            e.printStackTrace();
        }


        return null;

    }
}

Controller类

 @PostMapping("/upload")
    public SysResult upload(MultipartFile file){
    ImageVo imageVo =fileService.fileUpload(file);

        return SysResult.successful(imageVo);
    }

Nginx实现图片回显

文件删除业务接口

请求路径: http://localhost:8091/file/deleteFile
请求类型: delete
请求参数:
参数名称 参数说明 备注
virtualPath 文件上传的虚拟的路径 删除时需要磁盘路径一起删除
返回值结果:
参数名称 参数说明 备注
status 状态信息 200表示服务器请求成功 201表示服务器异常
msg 服务器返回的提示信息 可以为null
data 服务器返回的业务数据 可以为null

编辑FileController

 /**
     * 业务说明: 文件删除操作
     * URL地址:   http://localhost:8091/file/deleteFile
     * 请求类型:   delete
     * 参数:      virtualPath 虚拟路径
     * 返回值:    SysResult对象
     */
    @DeleteMapping("/deleteFile")
    public SysResult deleteFile(String virtualPath){

        fileService.deleteFile(virtualPath);
        return SysResult.success();
    }

编辑FileService

@Override
    public void deleteFile(String virtualPath) {
        String filePath = localDirPath + virtualPath;
        File file = new File(filePath);
        if(file.exists()){ //如果文件存在,则删除数据
            file.delete();
        }
    }

图片路径封装

路径分析

图片网络地址: https://img14.360buyimg.com/n0/jfs/t2/ac4a3f32ea776da3.jpg
协议://域名:80/虚拟地址
图片地址封装: http://image.jt.com:80/2021/11/11/uuid.jpg.

页面URL地址封装

package com.jt.service;

import com.jt.vo.ImageVO;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.FileNameMap;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

@Service
public class FileServiceImpl implements FileService{

    private String localDirPath = "E:/project3/images";
    private String preUrl = "http://image.jt.com";

    //1.校验图片类型   xxx.jpg   校验后缀是否为jpg
    @Override
    public ImageVO upload(MultipartFile file) {

        //1.1 获取文件名称  abc.jpg
        String fileName = file.getOriginalFilename();
        //1.2 全部转化为小写字母
        fileName = fileName.toLowerCase();
        //1.3正则校验是否为图片类型
        if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
            //图片类型 不匹配  程序应该终止
            return null;
        }

        //2.校验是否为恶意程序 怎么判断就是一张图 高度和宽度
        //2.1 通过图片对象进行处理
        try {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            int height = bufferedImage.getHeight();
            int width = bufferedImage.getWidth();
            if(height == 0 || width == 0){
                return null;
            }
            //3.将图片分目录存储 yyyy/MM/dd
            String dateDir = new SimpleDateFormat("/yyyy/MM/dd/")
                                .format(new Date());
            String dateDirPath = localDirPath + dateDir;
            File dirFile = new File(dateDirPath);
            if(!dirFile.exists()){
                dirFile.mkdirs();
            }

            //4.防止文件重名  动态生成UUID.类型
            //4.1 动态生成UUID
            String uuid = UUID.randomUUID().toString()
                              .replace("-","");
            //4.2 获取图片类型        abc.jpg    .jpg
            String fileType = fileName.substring(fileName.lastIndexOf("."));
            // uuid.jpg
            String newFileName = uuid + fileType;

            //5.实现文件上传 1.准备全文件路径  2. 封装对象实现上传
            String path = dateDirPath + newFileName;
            file.transferTo(new File(path));

            //6. 实现ImageVO数据的返回
            //6.1 准备虚拟路径 /2021/11/11/uuid.jpg
            String virtualPath = dateDir + newFileName;
            //6.2 准备URL地址  域名前缀 + 虚拟路径
            String url =  preUrl + virtualPath;
            System.out.println(url);
            return new ImageVO(virtualPath,url,newFileName);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void deleteFile(String virtualPath) {
        String filePath = localDirPath + virtualPath;
        File file = new File(filePath);
        if(file.exists()){ //如果文件存在,则删除数据
            file.delete();
        }
    }
}

动态为属性赋值

说明: 如果将属性写死到java类中,后期维护时 导致维护不方便.
优化: 可以通过@value注解动态赋值.
在这里插入图片描述

编辑properties配置文件
image.localDirPath=E:/project3/images
image.preUrl=http://image.jt.com

属性动态赋值

Nginx

Nginx介绍

Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。
其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、简单的配置文件和低系统资源的消耗而闻名。2011年6月1日,nginx 1.0.4发布。
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

Nginx-特点

占用内存少 不超过2M tomcat服务器占用内存 200M
并发能力强 3-5万次/秒 tomcat 支持的并发能力 220-260个/秒 调优1000个/秒
开发语言 C语言开发 tomcat是java写的
知识点:

  1. 并发能力: 多个用户同时访问服务器.
  2. 并行 计算机中的一种处理方式.

Nginx-代理

需求: 图片需要进行回显 难题: 网络地址需要与物理地址进行映射
网络地址: http://image.jt.com/2021/11/16/64e19fa13e474ecca28d64e85b0a9312.jpg
物理地址: E:\project3\images\2021\11\16/64e19fa13e474ecca28d64e85b0a9312.jpg

问题1: 能否将物理地址传给用户,用户通过物理地址直接访问!!! 不可以

图片获取的步骤:

  1. 用户通过网络地址访问服务器: http://image.jt.com/2021/11/16/64e19fa13e474ecca28d64e85b0a9312.jpg
  2. 通过某种机制,将域名动态转化为本地磁盘地址.http://image.jt.com转换为E:\project3\images
  3. 根据磁盘地址信息.找到图片 回传给用户,即用户可以查看到图片.

反向代理(nginx)

反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定。反向代理服务器通常可用来作为Web加速,即使用反向代理作为Web服务器的前置机来降低网络和服务器的负载,提高访问效率。

特点:

  1. 反向代理服务器介于用户和目标服务器之间
  2. 用户的资源从反向代理服务器中获取.
  3. 用户不清楚真实的服务器到底是谁. 保护了服务器的信息. 称之为服务器端代理.
    在这里插入图片描述

正向代理(扩展)

正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。

特点:

  1. 反向代理服务器介于用户和目标服务器之间
  2. 用户的资源从正向代理服务器中获取.
  3. 客户端通过正向代理服务器,指向目标服务器.(用户非常清楚的了解目标服务器的存在.) 服务器端不清楚到底是谁访问的服务器.以为只是代理服务器访问.

实际应用:
在这里插入图片描述

关于正向和反向说明

说明: 每一次请求服务器,都伴随着正向代理和反向代理.
正向主要提供网络服务, 反向主要提供数据支持.
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值