京淘项目案例

用户登录业务实现

用户登陆的业务本质就是输入账号密码,用来对比数据库中是否有。因为数据库存储密码经过加密,比如MD5。那么输入的密码查询前,也需要使用加密算法转换。如果账号密码无误,需要用UUID获取密匙。其他网页需要用路由守卫保护,有密匙才能通过
页面JS分析
在这里插入图片描述
用户登录JS
在这里插入图片描述

加密算法MD5

规则说明: MD5加密算法,只能由明文转化为密文. 不可以反向编译.
破解MD5加密算法:利用MD5密码与乱码一致性,用穷举法破解
在这里插入图片描述

关于秘钥说明

说明: 当用户登录之后,可以跳转到系统的首页. 到了系统首页之后,用户可以进行其它的业务操作. 系统如何判断正在操作业务的用户 已经登录?

业务说明:
一般在登录认证系统中,都会返回秘钥信息.来作为用户登录的凭证.
秘钥特点: 最好独一无二.

动态生成秘钥: UUID

 /**
     * 业务需求:
     *  1.将密码进行加密处理
     *  2.根据username/password 查询数据库获取数据.
     *  3. 有数据 用户名密码正确
     *     无数据 用户名和密码错误
     * @param user
     * @return
     */
    @Override
    public String login(User user) {
        //1.将密码加密处理
        String password = user.getPassword();
        //2.利用md5加密算法 进行加密
        String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
        user.setPassword(md5Pass);
        //3.查询数据库数据
        User userDB = userMapper.findUserByUP(user);
        if(userDB == null){
            //说明: 用户名和密码错误
            return null;
        }
            //说明: 用户名和密码正确,返回秘钥
        String uuid = UUID.randomUUID().toString()
                          .replace("-","");
        return uuid;
    }

关于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] 。

特点:

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

关于Cookie和Session说明

  • 手机银行的登录信息? Session存储. 数据安全性高
  • 腾讯视频会员登录信息? Cookie存储 1个月免密登录.
  • 公司的财务系统登录信息? Session存储
  • 购物系统的登录信息? Cookie存储.
    用户登录信息存储
//获取用户token信息
     let token = result.data
     window.sessionStorage.setItem("token",token)

在这里插入图片描述

用户登录模块实现

用户业务接口文档说明

  • 请求路径: /user/login
  • 请求方式: POST
  • 请求参数
    参数名称 参数说明 备注
    在这里插入图片描述
  • 响应数据 SysResult对象
    在这里插入图片描述

返回值格式如下:

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

编辑SysResult对象

package com.jt.controller;

import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.vo.SysResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/findAll")
    public List<User> findAll(){

        return userService.findAll();
    }

    public SysResult aa(){

        return new SysResult(201,"xxxx",null);
    }
}

编辑UserController

 /**
     *  URL地址: /user/login
     *  请求类型: post
     *  参数:    JSON串 username/password
     *  返回值:  SysResult对象
     */
    @PostMapping("/login")
    public SysResult login(@RequestBody User user){

        //返回值一个字符串 token
        String token = userService.login(user);
        if(token == null){
            return SysResult.fail(); //201
        }
        return SysResult.success(token); //200
    }

编辑UserService

/**
     * 业务需求:
     *  1.将密码进行加密处理
     *  2.根据username/password 查询数据库获取数据.
     *  3. 有数据 用户名密码正确
     *     无数据 用户名和密码错误
     * @param user
     * @return
     */
    @Override
    public String login(User user) {
        //1.将密码加密处理
        String password = user.getPassword();
        //2.利用md5加密算法 进行加密
        String md5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
        user.setPassword(md5Pass);
        //3.查询数据库数据
        User userDB = userMapper.findUserByUP(user);
        if(userDB == null){
            //说明: 用户名和密码错误
            return null;
        }
            //说明: 用户名和密码正确
        return "秘钥";
    }

编辑UserMapper

public interface UserMapper {

    @Select("select * from user")
    List<User> findAll();
    @Select("select * from user where username=#{username} and password=#{password}")
    User findUserByUP(User user);
}

系统跳转

系统首页跳转

编辑路由JS
在这里插入图片描述
首页跳转效果
在这里插入图片描述

路由导航守卫

需求说明
说明: 当用户在没有登录的条件下. 用户可以手动输入请求地址. 可以直接跳转项目. 这样的方式非常不安全.
解决方案: 前端通过拦截器 控制用户是否登录.
拦截器说明: 用户拦截的是URL中跳转的路径.
结果: 1.拦截 跳转到登录页面.
2.放行 跳转用户目标页面.
路由导航守卫
说明: 编辑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()
})

左侧菜单展现

需要完成的业务为,网页渲染时就查询一级菜单,并展示出来。点击一级菜单,可以触发展示对应的二级菜单
菜单为层级结构,菜单的数据库表为自关联。查询每次查询时都需要上一级的关联ID作为查询条件,该ID可以通过作用域插槽获取,发送给后端。
一对多关系,POJO类有一个集合属性。

搭建层级代码

表设计说明
在这里插入图片描述
关于Rights POJO说明

@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对象
    在这里插入图片描述

  • 响应数据如图所示
    在这里插入图片描述

父子关系封装/Sql语句写法
要求: 查询所有一级菜单和一级菜单所对应的二级菜单 要求关联查询

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

编辑RightsController

@RestController
@CrossOrigin
@RequestMapping("/rights")
public class RightsController {

    @Autowired
    private RightsService rightsService;

    /**
     * 查询一级二级数据
     * URL: /rights/getRightsList
     * 参数: 无
     * 返回值: SysResult(List<Rights>)
     */
    @GetMapping("/getRightsList")
    public SysResult getRightsList(){

        List<Rights> rights = rightsService.getRightsList();
        return SysResult.success(rights);
    }
}

编辑RightsService

@Service
public class RightsServiceImpl implements RightsService{

    @Autowired
    private RightsMapper rightsMapper;


    @Override
    public List<Rights> getRightsList() {

        return rightsMapper.getRightsList();
    }
}

编辑RightsMapper/xml映射文件
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>

页面效果展现
在这里插入图片描述
关于项目报错调试步骤
说明: 通过控制台 检查请求路径/响应信息/及JS报错信息. 后台服务器端口号固定 8091
在这里插入图片描述

关于页面跳转子级路由说明

现象说明
1.页面路由跳转 用户点击子级菜单时.页面将整个后端页面进行覆盖. 效果如下.
2.实际效果: 应该在首页的右侧 展现新的页面信息.
在这里插入图片描述
在这里插入图片描述

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

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

父子组件嵌套总结:

  1. 定义父级组件
    在这里插入图片描述

  2. 路由策略:
    在这里插入图片描述

  3. 如果需要嵌套 通过 router-view 进行占位, 通过children属性定义父子关系的结构. 当点击子组件时,会在父级组件的router-view中展现子组件.

首页嵌套规则
在Home组件中定义路由的占位符
在这里插入图片描述

定义父子组件的策略
在这里插入图片描述

用户管理

业务涉及分页查询,用户新增,信息修改,修操作时的数据回显,以及用户删除操作。

  • 新增时需要使用MD5加密。
  • 新增和修改操作都需要将时间写入。因为新增需要多写入个建立时间,为了时间的一致性。可以new 一个date对象
    更新操作新增或许修改,都需要用事务控制和加上更新时间。

用户列表展现

页面JS分析
生命周期函数

//利用钩子函数实现数据查询
    mounted(){
      this.getUserList()
    }

获取数据函数分析

async getUserList(){
        const {data: result} = await this.$http.get('/user/list',{
           params: this.queryInfo
        })
        if(result.status !== 200) return this.$message.error("用户列表查询失败")
        this.userList = result.data.rows
        this.total = result.data.total
        console.log("总记录数:"+this.total)
      },

业务接口说明

  • 请求路径: /user/list

  • 请求类型: GET

  • 请求参数: 后台使用PageResult对象接收

  • 请求案例: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10
    在这里插入图片描述

  • 响应参数: SysResult对象 需要携带分页对象 PageResult

  • PageResult 对象介绍
    在这里插入图片描述

  • 返回值效果

{"status":200,
  "msg":"服务器调用成功!",
  "data":
	{"query":"",
	"pageNum":1,
	"pageSize":2,
	"total":4,
	"rows":[
		{"created":"2021-02-18T11:17:23.000+00:00",
		 "updated":"2021-03-26T06:47:20.000+00:00",
		 "id":1,
		 "username":"admin",
		 "password":"a66abb5684c45962d887564f08346e8d",
		 "phone":"13111112222",
		 "email":"1235678@qq.com",
		 "status":true,
		 "role":null
		 },
		{"created":"2021-02-18T11:17:23.000+00:00",
		"updated":"2021-03-13T08:50:30.000+00:00",
		"id":2,
		"username":"admin123",
		"password":"a66abb5684c45962d887564f08346e8d",
		"phone":"13111112223",
		"email":"1235678@qq.com",
		"status":false,
		"role":null
		}
		]
	}
}

封装PageResult对象

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class PageResult {   //封装VO对象
    private String query;
    private Integer pageNum;
    private Integer pageSize;
    private Long    total;
    private Object  rows;
}

编辑UserController

/**
     * 业务说明:
     *  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个
     }

编辑UserService

 /**
     * 要求查询  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
     * @param pageResult
     * @return
     */
    @Override
    public PageResult getUserList(PageResult pageResult) {
        //1.记录总数 total
        long total = userMapper.getTotal();
        //2.分页后的数据
        int size = pageResult.getPageSize();
        int start = (pageResult.getPageNum() - 1) * size;
        List<User> rows = userMapper.findUserListByPage(start,size);
        return pageResult.setTotal(total).setRows(rows);
    }

编辑UserMapper接口

@Select("select * from user limit #{start},#{size}")
    List<User> findUserListByPage(@Param("start") int start,@Param("size") int size);

页面效果展现
在这里插入图片描述

分页查询补充

用户需求说明
说明: 用户的文本输入框,可能有值,也可能没有数据. 则在后端服务器中应该使用动态Sql的方式实现数据的查询.
在这里插入图片描述
编辑UserController

 /**
     * 业务说明:
     *  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个
     }

编辑UserService

 @Override
    public PageResult getUserList(PageResult pageResult) {
        //1.记录总数 total
        long total = userMapper.getTotal();
        //2.分页后的数据
        //2.1获取每页条数
        int size = pageResult.getPageSize();
        //2.2获取起始位置
        int start = (pageResult.getPageNum() - 1) * size;
        //2.3 获取用户查询的数据
        String query = pageResult.getQuery();
        List<User> rows = userMapper.findUserListByPage(start,size,query);
        return pageResult.setTotal(total).setRows(rows);
    }

编辑Mapper接口

List<User> findUserListByPage(@Param("start") int start,
                                  @Param("size") int size,
                                  @Param("query") String query);

编辑UserMapper.xml 映射文件

<mapper namespace="com.jt.mapper.UserMapper">
    <!--
        resultType: 适合单表查询
        resultMap:  1.多表关联查询  2.字段名称和属性不一致的时候使用
        if 判断条件
               test="query !=null and query !='' 表示同时不满足时条件成立
    -->
    <select id="findUserListByPage" resultType="User">
        select * from user
            <where>
                <if test="query !=null and query !='' ">username like "%"#{query}"%"</if>
            </where>
        limit #{start},#{size}
    </select>
</mapper>

完成状态修改

业务说明
说明: 通过开关 控制数据库中的 status=true/false 数据库中显示1/0 1/0 与 true/false 对象映射可以互相转化.
根据用户的ID 实现状态的修改.
在这里插入图片描述
前端JS分析

  • 知识点
    作用域插槽: 一般在表格数据展现时,可以动态获取当前行对象.
    用法:
    1.template
    2.slot-scope属性=“变量”
  • 页面JS分析
   <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>
  • 页面函数说明
async updateStatus(user){
         //实现用户状态修改  注意使用模版字符串  ES6中提出的新用法 ${key}
        //const {data: result} = await this.$http.put('/user/status/'+user.id+'/'+user.status)
        const {data: result} = await this.$http.put(`/user/status/${user.id}/${user.status}`)
        if(result.status !== 200) return this.$message.error("用户状态修改失败!")
        this.$message.success("用户状态修改成功!")
      },

业务接口文档说明

  • 请求路径 /user/status/{id}/{status}

  • 请求类型 PUT

  • 请求参数: 用户ID/状态值数据
    在这里插入图片描述

  • 返回值结果: 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);

用户新增操作

页面JS分析
编辑新增页面
在这里插入图片描述

新增页面JS分析
在这里插入图片描述

新增业务接口说明

  • 请求路径 /user/addUser

  • 请求类型 POST

  • 请求参数: 整个form表单数据封装为js对象进行参数传递
    在这里插入图片描述

  • 返回值结果: SysResult对象

{"status":200,"msg":"服务器调用成功!","data":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映射文件
编辑mapper接口

void addUser(User user);

编辑xml映射文件

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

修改操作数据回显

页面JS分析

  • 按钮点击事件
<el-button type="primary" icon="el-icon-edit" size="small" @click="updateUserBtn(scope.row)"></el-button>
  • 数据回显JS
async updateUserBtn(user){
        this.updateDialogVisible = true
        const {data: result} = await this.$http.get("/user/"+user.id)
        if(result.status !== 200) return this.$message.error("用户查询失败")
        this.updateUserModel = result.data
      },

页面接口文档

  • 请求路径: /user/{id}

  • 请求类型: GET

  • 返回值: SysResult对象
    在这里插入图片描述

  • JSON格式如下:

{
 "status":200,
 "msg":"服务器调用成功!",
 "data":{
	 "created":"2021-02-18T11:17:23.000+00:00",
	 "updated":"2021-05-17T11:33:46.000+00:00",
	 "id":1,
	 "username":"admin",
	 "password":"a66abb5684c45962d887564f08346e8d",
	 "phone":"13111112222",
	 "email":"1235678@qq.com",
	 "status":true,
	 "role":null
	 }
 }

编辑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);

页面效果展现
在这里插入图片描述

实现用户的更新操作

页面JS分析

  • 页面JS
    在这里插入图片描述

  • 发起Ajax请求
    在这里插入图片描述

修改的业务接口

  • 请求路径: /user/updateUser

  • 请求类型: PUT

  • 请求参数: User对象结构
    在这里插入图片描述

  • 返回值: SysResult对象
    在这里插入图片描述

  • JSON格式如下:

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

编辑UserController

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

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

编辑UserService

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

        userMapper.updateUser(user);
    }

编辑UserMapper

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

用户删除操作

页面JS修改
在这里插入图片描述
业务接口文档

  • 请求路径: /user/{id}

  • 请求类型: delete

  • 请求参数:
    在这里插入图片描述

  • 返回值: SysResult对象
    在这里插入图片描述

编辑UserController

 /**
     * 关于请求的小结
     *    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();
    }

编辑UserService

@Override
    public void deleteUserById(Integer id) {

        userMapper.deleteUserById(id);
    }

编辑UserMapper

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

商品管理

业务涉及商品分类展现,新增,删除,修改。更新操作新增或许修改,都需要用事务控制和加上更新时间。

  • 商品分类为树状结构,并且表关系涉及自关联,外键。
  • 在展现时,默认展现一级目录。点击可以打开下级内容,有可能二级为最后一级。二级继续点击可以获得三级信息
    遍历嵌套法:频繁访问数据库.导致数据库压力增大.严重时可能导致数据库服务器宕机. 不能接受的
  1. 查询出所有一级对象集合。
  2. 遍历一级对象结合,拿到一级对象,在根据一级对象获取一级ID,作为二级父ID,查询出二级对象集合
  3. 遍历二级对象结合,拿到二级对象,在根据二级对象获取二级ID,作为三级父ID,查询出三级对象集合
    Map集合封装法:查询出所有数据,将父ID封装为Map集合的key,将ID封装为Map结合的value
    新建封装方法
  4. 查询出所有对象,获取对象集合。
  5. 遍历对象集合,获取对象父ID
  6. 同一个父ID有多个子对象,所以在封装要前判断该父ID是否存过值。
  7. 存过:通过Map集合和父ID获取对应ID对象,直接传入
  8. 没存过:==建立一个实例为ArrayList的List集合,方便后面有序添加值。==往集合中封装对象,然后将父ID和集合封装到Map集合
  9. 返回封装Map集合
    在核心方法
  10. 调用封装方法,获取封装Map集合
  11. 根据参数判断是否是一级,是则通过Map获取一级对象集合。判断是否为二级,是则调用二级方法,返回封装了二级对象的一级对象集合
  12. 因为上面判断一二级并有返回值,所以不是三级下方代码不会执行,可以直接调用三级方法,返回封装了三级对象的一级集合
    在二级方法中
  13. 通过Map集合获取一级对象集合
  14. 遍历一级对象集合获取一级对象,并获得二级父ID。通过二级父ID获得二级对象集合
  15. 将二级集合封装到一级对象中
  16. 返回已封装二级的一级集合
  17. 在三级方法中,调用二级方法,获得已封装二级对象的一级集合
  18. 遍历一级集合得到一级对象,获得二级对象集合
  19. ==因为有时没有二级直接最后一级,所以要判断二级对象是否为空。==为空这跳过本次循环
  20. 不为空,遍历二级集合得到二级对象,获得三级对象集合,将三级对象集合封装到二级对象中
  21. 放回已封装三级的一级集合
  • ==因为商品分类为树状结构,所以只能从最低级对象一层一层删除。==非倒数两级业务,需要层层获取到倒数第二级集合。从而确保有每一级的ID或父ID

商品列表展现

编辑映射文件

  • 子查询Sql语句写法
/*查询一级菜单信息*/
SELECT * FROM rights WHERE parent_id = 0
/* 查询从表数据 */
SELECT * FROM rights WHERE parent_id = 3
  • xml映射文件写法
 <!--利用子查询的方式实现数据获取
        1.查询主表信息
     -->
    <select id="getRightsList" resultMap="rightsRM">
        select * from rights where parent_id = 0
    </select>
    <resultMap id="rightsRM" type="Rights" autoMapping="true">
        <!--主键信息-->
        <id property="id" column="id"></id>
        <collection property="children" ofType="Rights"
                    select="findChildren" column="id"/>
    </resultMap>
    <select id="findChildren" resultType="Rights">
        select * from rights where parent_id = #{id}
    </select>

商品分类页面跳转
说明: 编辑路由 index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../components/Login.vue'
import ElementUI from '../components/ElementUI.vue'
import Home from '../components/Home.vue'
import User from '../components/user/user.vue'
import ItemCat from '../components/items/ItemCat.vue'
//使用路由机制
Vue.use(VueRouter)
const routes = [
  {path: '/', redirect: '/login'},
  {path: '/login', component: Login},
  {path: '/elementUI', component: ElementUI},
  {path: '/home', component: Home, children: [
    {path: '/user', component: User},
     {path: '/itemCat', component: ItemCat}
  ]}
]

页面效果展现
在这里插入图片描述

完成商品分类业务

页面JS分析

  • 生命周期函数
//定义初始化函数
    created() {
      //默认获取商品分类列表数据
      this.findItemCatList()
    },
  • 获取数据函数说明
async findItemCatList() {
        const {
          data: result
        } = await this.$http.get("/itemCat/findItemCatList/3")
        if (result.status !== 200) return this.$message.error("获取商品分类列表失败!!")
        this.itemCatList = result.data
      },

业务接口文档

  • 请求路径: /itemCat/findItemCatList/{level}

  • 请求类型: get

  • 请求参数: level
    在这里插入图片描述

  • 业务说明: 查询3级分类菜单数据 要求三层结构嵌套

  • 返回值: SysResult对象
    在这里插入图片描述

商品分类表结构说明

  • 表结构
    在这里插入图片描述
    sql案例练习
/*所有的一级菜单 parent_id=0*/
SELECT * FROM item_cat WHERE parent_id = 0
/*查询汽车用户的二级菜单*/
SELECT * FROM item_cat WHERE parent_id = 249
/*查询车载电器的三级菜单*/
SELECT * FROM item_cat WHERE parent_id = 281

小结: 商品分类表,通过parent_id 来指定父子级关系.
编辑ItemCatController

@RestController
@CrossOrigin
@RequestMapping("/itemCat")
public class ItemCatController {

    @Autowired
    private ItemCatService itemCatService;

    /**
     * 需求: 查询商品分类信息
     * 参数: /{level}   1一级  2 一二级  3 一二三级
     * url: /itemCat/findItemCatList/{level}  restFul
     * 返回值: SysResult(3级列表信息)
     */
    @GetMapping("findItemCatList/{level}")
    public SysResult findItemCatList(@PathVariable Integer level){

        List<ItemCat> itemCatList = itemCatService.findItemCatList(level);
        return SysResult.success(itemCatList);
    }

}

编辑ItemCatService

package com.jt.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.ItemCatMapper;
import com.jt.pojo.ItemCat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ItemCatServiceImpl implements ItemCatService{

    @Autowired
    private ItemCatMapper itemCatMapper;

    /**
     * 步骤1.查询一级菜单列表
     * @param level
     * @return
     */
    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        //1.查询一级菜单
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("parent_id",0);
        List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
        //2.查询二级菜单 二级数据是一级数据的子级 封装到一级数据中.
        for(ItemCat oneItemCat : oneList){
            int oneId = oneItemCat.getId(); //一级对象ID
            //清空原始条件  必须有
            queryWrapper.clear();
            queryWrapper.eq("parent_id",oneId);
            List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
            for(ItemCat twoItemCat : twoList){
                //获取二级分类ID
                int twoId = twoItemCat.getId();
                //查询三级列表信息
                queryWrapper.clear();
                queryWrapper.eq("parent_id",twoId);
                List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper);
                //将三级列表 封装到二级对象中
                twoItemCat.setChildren(threeList);
            }
            //将二级数据封装到一级对象中
            oneItemCat.setChildren(twoList);
        }
        return oneList;
    }
}

上述案例分析

  • 上述的案例 采用多级循环的方式. 将来会耗费服务器资源 100次 内层100次 总循环1万次. 暂时可以接受
  • 上述的代码 频繁访问数据库.导致数据库压力增大.严重时可能导致数据库服务器宕机. 不能接受的
    优化策略: 降低数据库访问的次数
    采用数据结构优化代码
    思路:
  1. 用户查询所有的数据库信息. (1-2-3所有数据)
  2. 数据结构 Map<k,v> key唯一的, value可以任意类型.
    思路: Map<parentId,List>
    例子:
  3. Map<0,List<一级ItemCat对象>>
  4. Map<249,List<二级ItemCat对象>>
  5. Map<281,List<三级ItemCat对象>>
    利用map 封装父子关系.

代码具体实现

@Service
public class ItemCatServiceImpl implements ItemCatService{

    @Autowired
    private ItemCatMapper itemCatMapper;

    /**
     * 利用Map集合封装所有的数据库记录
     * 封装数据:
     *      1.遍历所有的数据信息.
     *      2.获取每一个parentId的值.
     * 例子:
     *      1.{id=1,parentId=0,name="张三"}
     *      2.{id=2,parentId=0,name="李四"}
     *      3.{id=3,parentId=1,name="王五"}
     *      Map= {
     *          key : value
     *          0   : List[张三对象,李四对象.....],
     *          1   : List[王五对象......]
     *      }
     * @return
     */
    public Map<Integer,List<ItemCat>> getMap(){
        Map<Integer,List<ItemCat>> map = new HashMap<>();
        //1.查询所有的数据库信息
        List<ItemCat> itemCatList = itemCatMapper.selectList(null);
        //2.将数据封装到map集合中
        for (ItemCat itemCat : itemCatList){
            Integer key = itemCat.getParentId(); //获取parentId当做key
            //3.判断map集合中是否有值.
            if(map.containsKey(key)){
                //有值: 获取List集合,将自己追加到其中
                map.get(key).add(itemCat);
            }else{
                //没值: 添加数据.将自己作为第一个元素填充
                List<ItemCat> list = new ArrayList<>();
                list.add(itemCat);
                map.put(key,list);
            }
        }
        return map;
    }

    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        long startTime = System.currentTimeMillis();
        Map<Integer,List<ItemCat>> map = getMap();
        //根据level获取子级信息
        if(level == 1){ //只获取一级列表信息
            return map.get(0);
        }
        if(level == 2){ //获取一级和二级数据
            return getTwoList(map);
        }
        List<ItemCat> oneList = getThreeList(map);
        long endTime = System.currentTimeMillis();
        System.out.println("优化前的耗时: 500ms,优化后耗时:"+(endTime - startTime)+"ms");
        return oneList;
    }

    //获取三级列表信息  先获取1级数据,再获取2级数据.再获取3级数据

    private List<ItemCat> getThreeList(Map<Integer, List<ItemCat>> map) {
        //1.调用2级菜单方法.
        List<ItemCat> oneList = getTwoList(map);
        //2.实现思路 遍历一级集合,获取二级数据. 封装三级菜单
        for(ItemCat oneItemCat : oneList){
            //2.1 获取二级数据
            List<ItemCat> twoList = oneItemCat.getChildren();
            if(twoList == null || twoList.size()==0){
                //判断二级集合是否为null.如果为null,表示没有二级菜单.
                continue;
            }
            for (ItemCat twoItemCat : twoList){
                int twoId = twoItemCat.getId();
                List<ItemCat> threeList = map.get(twoId);
                twoItemCat.setChildren(threeList);
            }
        }
        return oneList;
    }

    //通过map集合 获取一级二级菜单信息.
    private List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
        List<ItemCat> oneList = map.get(0);
        //获取二级信息,应该先遍历一级集合
        for (ItemCat oneItemCat : oneList){
            int oneId = oneItemCat.getId();
            //根据一级Id,获取二级集合
            List<ItemCat> twoList = map.get(oneId);
            oneItemCat.setChildren(twoList);
        }
        return oneList;
    }


    /**
     * 步骤1.查询一级菜单列表
     * @param level
     * @return
     */
   /* @Override
    public List<ItemCat> findItemCatList(Integer level) {
        long startTime = System.currentTimeMillis();
        //1.查询一级菜单
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("parent_id",0);
        List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
        //2.查询二级菜单 二级数据是一级数据的子级 封装到一级数据中.
        for(ItemCat oneItemCat : oneList){
            int oneId = oneItemCat.getId(); //一级对象ID
            //清空原始条件  必须有
            queryWrapper.clear();
            queryWrapper.eq("parent_id",oneId);
            List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
            for(ItemCat twoItemCat : twoList){
                //获取二级分类ID
                int twoId = twoItemCat.getId();
                //查询三级列表信息
                queryWrapper.clear();
                queryWrapper.eq("parent_id",twoId);
                List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper);
                //将三级列表 封装到二级对象中
                twoItemCat.setChildren(threeList);
            }
            //将二级数据封装到一级对象中
            oneItemCat.setChildren(twoList);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("耗时:"+ (endTime - startTime)+"ms");
        return oneList;
    }*/
}

商品分类新增实现

页面JS分析

//定义商品分类新增对象
        itemCatForm: {
          name: '', //定义商品分类名称
          parentId: 0, //默认父级ID=0
          level: 1 //默认是一级菜单
        },
		
		async addItemCatForm() {
        //先将整个表单进行校验
        this.$refs.itemCatFormRef.validate(async validate => {
          if (!validate) return
          const {
            data: result
          } = await this.$http.post("/itemCat/saveItemCat", this.itemCatForm)
          if (result.status !== 200) return this.$message.error("新增商品分类失败")
          this.$message.success("新增商品分类成功!!!")
          //新增成功,则刷新分类列表信息
          this.findItemCatList();
          this.addItemCatDialogVisible = false
        })
      },

商品分类新增接口文档

  • 请求路径: /itemCat/saveItemCat

  • 请求类型: post

  • 请求参数: 表单数据
    在这里插入图片描述

  • 返回值: SysResult对象
    在这里插入图片描述

编辑ItemCatController

/**
     * 业务需求:  实现商品分类新增
     * URL:  /itemCat/saveItemCat
     * 类型: post
     * 参数: {"name":"AAAAAA","parentId":0,"level":1} json串
     * 返回值: SysResult对象
     */
    @PostMapping("/saveItemCat")
    public SysResult saveItem(@RequestBody ItemCat itemCat){

        itemCatService.saveItem(itemCat);
        return SysResult.success();
    }

编辑ItemCatService

@Override
    @Transactional  //事务控制
    public void saveItem(ItemCat itemCat) {
        Date date = new Date();
        itemCat.setStatus(true).setCreated(date).setUpdated(date);//启动
        itemCatMapper.insert(itemCat);
    }

商品分类删除操作

删除业务接口

  • 请求路径: /itemCat/deleteItemCat

  • 请求类型: delete

  • 业务描述: 当删除节点为父级时,应该删除自身和所有的子节点

  • 请求参数:
    在这里插入图片描述

  • 返回值结果 SysResult对象
    在这里插入图片描述

前端页面JS

1. 页面JS
<el-button type="danger" icon="el-icon-delete" @click="deleteItemCatBtn(scope.row)">删除</el-button>


2. 发起Ajax请求
//传递分类id
        const {data: result} = await this.$http.delete("/itemCat/deleteItemCat",{params:{id:itemCat.id,level:itemCat.level}})
          if(result.status !== 200) return this.$message.error("删除商品分类失败")
          this.$message.success("删除数据成功")
          //删除成功之后,刷新页面数据
          this.findItemCatList()       

编辑ItemCatController

 /**
     * 完成商品分类的删除操作
     * 1. 编辑URL: /itemCat/deleteItemCat
     * 2. 参数: id/level
     * 3. 返回值: SysResult()
     */
    @DeleteMapping("/deleteItemCat")
    public SysResult deleteItemCat(Integer id,Integer level){

        itemCatService.deleteItemCat(id,level);
        return SysResult.success();
    }

编辑ItemCatService

//删除商品分类数据
    @Override
    public void deleteItemCat(Integer id, Integer level) {
        //判断是否为3级菜单
        if(level == 3){
            itemCatMapper.deleteById(id);
        }

        if(level == 2){
            //如果是二级,应该先获取三级数据之后删除,再删除自己
            //delete from item_cat where parent_id=#{id} or id = #{id}
            QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("parent_id",id)
                        .or()
                        .eq("id",id);
            itemCatMapper.delete(queryWrapper);
        }

        /**
         * 如何删除一级菜单?
         *  1.获取二级ID
         *  终极sql: delete from item_cat where parent_id in (twoIds)
         *          or  id in (twoIds)
         *          or  id = #{id}
         */
        if(level == 1){
            QueryWrapper<ItemCat> queryWrapper = new QueryWrapper();
            queryWrapper.eq("parent_id",id);
            List twoIds = itemCatMapper.selectObjs(queryWrapper);
            //清空数据
            queryWrapper.clear();
            //规则: 如果2级菜单有值,才会删除 2级和三级
            queryWrapper.in(twoIds.size()>0,"parent_id",twoIds)
                        .or()
                        .in(twoIds.size()>0,"id",twoIds)
                        .or()
                        .eq("id",id);
            itemCatMapper.delete(queryWrapper);
        }
    }

商品分类修改操作

页面JS分析

1.指定修改的按钮
	<el-button type="success" icon="el-icon-edit" @click="updateItemCatBtn(scope.row)">编辑</el-button>
	
	2. 数据的回显
	//由于有层级关系,所有修改只能修改名称
      updateItemCatBtn(itemCat) {
        this.updateItemCatForm = itemCat
        this.updateItemCatDialogVisible = true
      },

	3. 修改页面的JS
	<el-dialog title="修改商品分类" :visible.sync="updateItemCatDialogVisible" width="50%">
      <!-- 定义分类表单 -->
      <el-form :model="updateItemCatForm" :rules="rules" ref="upDateItemCatForm" label-width="100px">
        <el-form-item label="分类名称:" prop="name">
          <el-input v-model="updateItemCatForm.name"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="updateItemCatDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="updateItemCat">确 定</el-button>
      </span>
    </el-dialog>
     
     4. 修改按钮的JS
       async updateItemCat() {
        //修改商品分类信息
        const {data: result} =
          await this.$http.put('/itemCat/updateItemCat', this.updateItemCatForm)
        if (result.status !== 200) return this.$message.error("更新商品分类失败")
        this.$message.success("更新商品分类成功")
        this.findItemCatList();
        this.updateItemCatDialogVisible = false;
      },

页面接口文档

  • 请求路径: /itemCat/updateItemCat
  • 请求类型: put
  • 请求参数: 表单数据 ItemCat对象
  • 返回值: SysResult对象
    在这里插入图片描述
    数据解析:
    在这里插入图片描述
    编辑ItemCatController
/**
     * 修改商品分类名称
     * URL: /itemCat/updateItemCat
     * 参数: 整个form表单  JSON串
     * 返回值: SysResult对象
     */
    @PutMapping("/updateItemCat")
    public SysResult updateItemCat(@RequestBody ItemCat itemCat){

        itemCatService.updateItemCat(itemCat);
        return SysResult.success();
    }

编辑ItemCatService

 //由于页面只修改的name名称.所以sql也只修改name/updated
    @Override
    @Transactional
    public void updateItemCat(ItemCat itemCat) {
        //用户只修改name,updated by id
        ItemCat temp = new ItemCat();
        temp.setId(itemCat.getId())
                .setName(itemCat.getName())
                .setUpdated(new Date());
        itemCatMapper.updateById(temp);
    }

商品模块业务实现

业务涉及商品信息展示,删除,修改,新增
商品信息别存于多个表,子表ID跟随主表ID,所以操作都有可能涉及多表。
接收信息时会需要一个大对象,包含基本信息对象和详细信息对象。
详细信息用的时富文本编辑器,传入后端的是字符串。为了存储大量字符,详细信息表需要用到mediumtext类型
==主键回显问题:==在新增操作时就会出现入库之后才有主键的问题,使用MP的情况下可以自动化回显
图片,视频存储一般不使用数据库,而是指定对应的文档储存。
前端图片向后端传输为输入流的方式传输,接收图片时需要用流对象接收。普通流需要手动关闭,代码繁琐,所以SpringMVC一般使用MultipartFile对象接收
文件传输四步:

  1. 文件类型校验,使用正则表达式
  2. 防止恶意程序,如:图片的话可以检查图片宽高
  3. 分目录储存,可以按类型分或者按时间分
  4. 自定义文件名,使用UUID

商品页面跳转

编辑index.js的路由文件

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../components/Login.vue'
import ElementUI from '../components/ElementUI.vue'
import Home from '../components/Home.vue'
import User from '../components/user/user.vue'
import ItemCat from '../components/items/ItemCat.vue'
import Item from '../components/items/Item.vue'
//使用路由机制
Vue.use(VueRouter)
const routes = [
  {path: '/', redirect: '/login'},
  {path: '/login', component: Login},
  {path: '/elementUI', component: ElementUI},
  {path: '/home', component: Home, children: [
     {path: '/user', component: User},
     {path: '/itemCat', component: ItemCat},
     {path: '/item', component: Item}
  ]}
]

页面效果
在这里插入图片描述

构建商品层级代码

item 表设计
在这里插入图片描述
编辑Item POJO

@Data
@Accessors(chain = true)
@TableName("item")
public class Item extends BasePojo{
    @TableId(type= IdType.AUTO) 
    private Integer id;         //商品Id号
    private String title;       //商品标题信息
    private String sellPoint;   //卖点信息
    private Integer price;      //商品价格
    private Integer num;        //商品数量
    private String images;       //商品图片
    private Integer itemCatId;  //商品分类ID号
    private Boolean status;     //状态信息    0 下架 1 上架
}

编辑层级代码结构
在这里插入图片描述

完成商品列表展现

页面分析

//1. 生命周期函数
 created() {
      //1.获取商品列表数据
      this.getItemList()
    },

//2. 调用 this.getItemList()
      async getItemList() {
        const {data: result} =
            await this.$http.get("/item/getItemList", {
          params: this.queryItemInfo
        })
        if (result.status !== 200) return this.$message.error("商品列表查询失败")
        this.itemList = result.data.rows
        this.total = result.data.total
      },

接口文档说明

  • 请求路径: /item/getItemList?query=&pageNum=1&pageSize=10

  • 请求类型: get

  • 请求参数: 使用pageResult对象接收
    在这里插入图片描述

  • 返回值结果:
    在这里插入图片描述

编辑ItemController

@RestController
@CrossOrigin
@RequestMapping("/item")
public class ItemController {

    @Autowired
    private ItemService itemService;

    /**
     * 业务: 实现商品的分页查询
     * URL: /item/getItemList?query=&pageNum=1&pageSize=10
     * 参数: query=&pageNum=1&pageSize=10
     * 返回值: SysResult(PageResult)
     */
    @GetMapping("/getItemList")
    public SysResult getItemList(PageResult pageResult){//3
        //3+2(总记录数,分页结果)
        pageResult  = itemService.getItemList(pageResult);
        return SysResult.success(pageResult);//5
    }
}

编辑ItemService

@Service
public class ItemServiceImpl implements ItemService{

    @Autowired
    private ItemMapper itemMapper;

    /**
     *  要求: 3+2(总记录数,分页结果)
     *  关于selectPage(参数说明)
     *   参数1: page MP提供的分页对象
     *   参数2: 条件构造器
     * @param pageResult
     * @return
     */
    @Override
    public PageResult getItemList(PageResult pageResult) {
        //1.构建分页对象  参数1: 第几页   参数2: 多少条
        Page<Item> page = new Page<>(pageResult.getPageNum(),pageResult.getPageSize());
        //2.准备条件构造器 构建模糊查询
        QueryWrapper queryWrapper = new QueryWrapper();
        String query = pageResult.getQuery();
        boolean flag = StringUtils.hasLength(query);
        queryWrapper.like(flag,"title",query);

        //3.根据MP查询 实现分页数据的自动封装
        page = itemMapper.selectPage(page,queryWrapper);

        //4.获取数据,返回分页对象
        long total = page.getTotal();
        //获取分页结果
        List<Item> rows = page.getRecords();
        return pageResult.setTotal(total).setRows(rows);
    }
}

编辑分页配置类

@Configuration  //这是配置类
public class MybatisConfig {

    //需要通过配置文件 指定数据库类型.
    // 最新版
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));
        return interceptor;
    }
}

页面效果展现
在这里插入图片描述

商品状态的修改

页面JS分析

<template slot-scope="scope">
            <el-switch v-model="scope.row.status" active-color="#13ce66" inactive-color="#ff4949"
              @change="updateStatus(scope.row)"></el-switch>
        </template>
		
		async updateStatus(item) {
        const { data: result} =
        await this.$http.put("/item/updateItemStatus", {
          id: item.id,
          status: item.status
        })
        if (result.status !== 200) return this.$message.error("更新状态失败")
        this.$message.success("更新状态成功")
      },

业务接口文档

  • 请求路径: /item/updateItemStatus

  • 请求类型: put

  • 请求参数: 使用对象接收
    在这里插入图片描述

  • 返回值结果:
    在这里插入图片描述

编辑ItemController

 /**
     * 修改商品的状态信息
     * URL: /item/updateItemStatus
     * 参数: JSON串 {id:xx,status:xx}
     * 返回值: SysResult对象
     */
    @PutMapping("/updateItemStatus")
    public SysResult updateItemStatus(@RequestBody Item item){

        itemService.updateItemStatus(item);
        return SysResult.success();
    }

编辑ItemService

 @Override
    @Transactional //控制事务
    public void updateItemStatus(Item item) {

        itemMapper.updateById(item);
    }

商品删除操作

页面分析

//根据id删除数据
          const {data: result} = await this.$http.delete("/item/deleteItemById", {
            params: {
              id: item.id
            }
          })
          if (result.status !== 200) return this.$message.error("商品删除失败")
          this.$message.success("商品删除成功")
          //重新获取商品列表信息
          this.getItemList()

业务接口文档

  • 请求路径: /item/deleteItemById

  • 请求类型: delete

  • 请求参数:
    在这里插入图片描述

  • 返回值结果:
    在这里插入图片描述

编辑ItemController

 /**
     * 业务需求: 根据Id 删除数据
     * URL: /item/deleteItemById
     * 参数: id
     * 返回值: SysResult对象
     */
    @DeleteMapping("/deleteItemById")
    public SysResult deleteItemById(Integer id){

        itemService.deleteItemById(id);
        return SysResult.success();
    }

编辑ItemService

@Override
    @Transactional
    public void deleteItemById(Integer id) {
    
        itemMapper.deleteById(id);
        itemDescMapper.deleteById(id);
    }

商品新增操作

商品基本信息入库

实现页面跳转

const routes = [
  {path: '/', redirect: '/login'},
  {path: '/login', component: Login},
  {path: '/elementUI', component: ElementUI},
  {path: '/home', component: Home, children: [
     {path: '/user', component: User},
     {path: '/itemCat', component: ItemCat},
     {path: '/item', component: Item},
     {path: '/item/addItem', component: AddItem}
  ]}
]

页面效果:

在这里插入图片描述

新增页面JS
表结构说明:

  1. 商品的基本信息 保存到item表
  2. 商品的详细信息 保存到item_desc表中.
 /* 添加商品按钮 */
      async addItemBtn(){
        //console.log(this.addItemForm)

        //1.完成表单校验
        this.$refs.addItemFormRef.validate( valid => {
          if(!valid) return this.$message.error("请输入商品必填项")
        })

        //2.完成商品参数的封装
        //2.0 将商品价格扩大100倍
        this.addItemForm.price = this.addItemForm.price * 100
        //2.1 将商品图片的数据转化为字符串
        this.addItemForm.images = this.addItemForm.images.join(",")

        //2.5 实现商品数据提交 用一个大对象 包裹2个小对象
        let submitAddItem = {
          item : this.addItemForm,
          itemDesc: this.itemDesc
        }
        //console.log(submitAddItem)
        let {data: result} = await this.$http.post("/item/saveItem",submitAddItem)
        if(result.status !== 200) return this.$message.error("商品添加失败")
        this.$message.success("商品添加成功")

        //2.5添加完成之后,将数据重定向到商品展现页面
        this.$router.push("/item")
      }

业务接口说明

  • 请求路径: http://localhost:8091/item/saveItem
  • 请求类型: post
  • 前端传递参数分析
{
		item: {
			images: "/2021/05/20/da0c1d4781c1499399f090da8b60f359.jpg,/2021/05/20/2ac1c34776a7465887eb019655354c3c.jpg"
			itemCatId: 560
			num: "100"
			price: 718800
			sellPoint: "【华为官方直供,至高12期免息0首付,原装正品】送华为原装无线充+运动蓝牙耳机+蓝牙音箱+三合一多功能数据线+钢化膜等!"
			title: "华为P40 Pro 5G手机【12期免息可选送豪礼】全网通智能手机"
		},
		itemDesc: {
				itemDesc: "<ul><li>品牌:&nbsp;<a href=https://list.jd.com/list.html".......      "
		}
	}
  • 请求参数: 使用ItemVO对象接收
    在这里插入图片描述

  • ItemVO参数详解:

  • Item对象
    在这里插入图片描述

  • itemDesc 对象

  • 为了降低商品提交代码的耦合性,将大字段信息详情,采用ItemDesc对象进行封装
    在这里插入图片描述

  • 返回值结果:
    在这里插入图片描述

编辑ItemController

 /**
     * 完成商品新增操作
     * 1.URL地址  http://localhost:8091/item/saveItem
     * 2.参数     post   itemVO JSON串
     * 3.返回值   SysResult对象
     */
    @PostMapping("/saveItem")
    public SysResult saveItem(@RequestBody ItemVO itemVO){

        itemService.saveItem(itemVO);
        return SysResult.success();
    }

编辑ItemService

@Override
    @Transactional
    public void saveItem(ItemVO itemVO) {
        Item item = itemVO.getItem();
        //设定状态
        item.setStatus(true);
        itemMapper.insert(item);
    }

商品详情入库

富文本编辑器
说明: 富文本可以在页面中,实现 “所见即所得” 的效果
在这里插入图片描述
引入步骤
引入js

/* 导入富文本编辑器 */
import VueQuillEditor from 'vue-quill-editor'

/* 导入富文本编辑器对应的样式 */
import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme
import 'quill/dist/quill.bubble.css' // for bubble theme

/* 将富文本编辑器注册为全局可用的组件 */
Vue.use(VueQuillEditor)

使用富文本编辑器

<!-- 定义富文本编辑器-->
 <quill-editor ref="myQuillEditor" v-model="itemDesc.itemDesc">
 </quill-editor>

关于ItemDesc 的说明
说明: 由于Item和ItemDesc 是典型的一对一. 所以要求 item.id = itemDesc.id

@Data
@Accessors(chain = true)
@TableName("item_desc")
public class ItemDesc extends BasePojo{
    @TableId
    private Integer id;
    private String itemDesc;
}

编辑ItemDescMapper

public interface ItemDescMapper extends BaseMapper<ItemDesc>{

}

编辑ItemService

 /**
     * 问题: id是主键自增. 入库之后才有主键所以
     *      应该让主键动态回显
     * 1.Mybatis 动态实现回显
     *      <insert id="xxxx" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
     *         insertinto xxxx
     *     </insert>
     * 2.MP是mybatis的增强版本.所以可以实现自动的主键回显!!!
     * @param itemVO
     */
    @Override
    @Transactional
    public void saveItem(ItemVO itemVO) {
        Item item = itemVO.getItem();
        //设定状态
        item.setStatus(true);
        itemMapper.insert(item);
        //获取商品详情
        ItemDesc itemDesc = itemVO.getItemDesc();
        itemDesc.setId(item.getId());
        itemDescMapper.insert(itemDesc);
    }

修改表类型.
说明: 为了存储大字段.修改数据库类型

在这里插入图片描述

商品修改操作

编辑页面HTML

//1. 定义页面html
<!-- 定义商品修改的对话框 -->
    <!-- 定义商品修改的对话框 -->
    <el-dialog title="商品修改" :visible.sync="updateDialogVisible" width="60%">
      <!-- 准备修改的表单-->
      <el-form :model="updateItem" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
        <el-form-item label="标题信息" prop="title">
          <el-input v-model="updateItem.title"></el-input>
        </el-form-item>
        <el-form-item label="卖点信息" prop="sellPoint">
          <el-input v-model="updateItem.sellPoint"></el-input>
        </el-form-item>
        <el-form-item label="价格信息" prop="price">
          <el-input v-model="updateItem.price"></el-input>
        </el-form-item>
        <el-form-item label="数量信息" prop="num">
          <el-input v-model="updateItem.num"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="updateDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
      </span>
    </el-dialog>
	
	//2. 定义页面JS
	updateDialogVisible: false,
        updateItem: {},
        //准备一个校验规则
        rules: {
          title: [
                  { required: true, message: '请输入商品标题信息', trigger: 'blur' },
                  { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
                ],
          sellPoint: [
                  { required: true, message: '请输入商品卖点信息', trigger: 'blur' },
                  { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
                ],
          price: [
                  { required: true, message: '请输入商品价格信息', trigger: 'blur' },
                  { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
                ],
          num: [
                  { required: true, message: '请输入商品数量信息', trigger: 'blur' },
                  { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
                ],
        }

	  //定义JS按钮
	  updateItemBtn(item){
        	console.log("扩展案例,自己实现 只需要修改 标题/卖点/价格/数量")
        	this.updateDialogVisible = true
        	this.updateItem = item
        	this.updateItem.price = (this.updateItem.price / 100).toFixed(2)
      }

商品图片上传(不完全)

编辑页面

//1. 官网图片JS说明
  <!-- 图片上传的JS
        1. action: 代表图片上传的地址url
        2. file-list: 图片列表数据的集合[{name:"xx",url:"xxx"},{}]
        3. 钩子函数: 满足某些条件时触发.
        4. on-preview 当点击已上传列表的信息时触发
        5. on-remove  当移除列表中的图片时触发
      -->
      <el-upload
        class="upload-demo"
        action="https://jsonplaceholder.typicode.com/posts/"
        :on-preview="handlePreview"
        :on-remove="handleRemove"
        :file-list="fileList"
        list-type="picture">
        <el-button size="small" type="primary">点击上传</el-button>
        <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
      </el-upload>
2. 页面JS补充知识
   handlePreview(){
      console.log("触发查看函数!!!!")
    },
    handleRemove(){
      console.log("移除时触发!!!!")
    }

图片上传项目说明

<!--.文件上传组件说明
                1.action: 上传图片地址 http://localhost:8091/xxx/xxx
                2.on-preview 点击图片时触发
                3.on-remove  移除图片时触发
                4.on-success 图片上传成功时触发
                5.multiple   可以支持多张图片上传
                6.drag       是否允许拖拽
              二.请求类型:    一般上传字节信息时,首选post请求
              三.上传文件key  说明: 文件上传时的key=file.
              				 后端接收数据时采用file接收.
            -->
            <el-upload class="upload-demo" :action="uploadUrl" :on-preview="handlePreview" :on-remove="handleRemove"
              :on-success="handleSuccess" list-type="picture" multiple drag>
              <el-button size="small" type="primary">点击上传</el-button>
              <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>

//定义文件上传路径地址
uploadUrl: "http://localhost:8091/file/upload",

图片上传接口文档说明

  • 请求路径: http://localhost:8091/file/upload

  • 请求类型: post

  • 请求参数:
    在这里插入图片描述

  • 返回值结果:
    在这里插入图片描述

  • ImageVO对象说明
    在这里插入图片描述

编辑ImageVO

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ImageVO {

    private String virtualPath; //图片虚拟路径 动态的路径
    private String urlPath;  //图片回显的URL地址
    private String fileName; //文件上传后的文件名称
}

编辑ItemController

@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {

    /**
     * 业务说明: 实现图片上传
     * URL: http://localhost:8091/file/upload
     * 类型: post
     * 参数: file 字节信息
     * 返回值: SysResult.success()
     * 扩展:
     *      一般情况下:
     *          一般前端向后端服务器发送字节信息.由外到内实现数据传输.
     *      采用输入流信息. InputStream file
     *          使用字节流的弊端: 1.必须手动关闭, 2.代码操作繁琐
     *          底层代码的实现.
     *       SpringMVC高级API  MultipartFile 专门处理IO流操作
     *  文件上传步骤:
     *        1.获取文件名称
     *        2.准备文件上传的目录
     *        3.判断目录是否存在  存在目录: 实现上传  没有目录:创建目录
     *        4.利用工具API方法,实现文件上传.
     *  注意事项: MultipartFile 默认支持1M的数据
     */
     @PostMapping("/upload")
     public SysResult upload(MultipartFile file) throws IOException {
        //1.获取文件名称
         String fileName = file.getOriginalFilename();
         //2.准备磁盘地址
         String dirPath = "E:/project3/images/";
         //3.将这个文件目录 封装为File对象
         File dirFile = new File(dirPath);
         //4.判断对象是否存在
         if(!dirFile.exists()){
            //如果文件目录不存在,则创建目录
             dirFile.mkdirs(); //表示多级目录上传.
         }
         //5.封装文件全路径 E:xxx/xxx/a.jpg
         String path = dirPath + fileName;
         File allFile = new File(path);
         //6.实现文件上传 将IO流按照指定的对象格式进行输出.
         file.transferTo(allFile);
         return SysResult.success();
     }
}

文件上传实现

编辑FileController

@RestController 
@CrossOrigin 
@RequestMapping("/file") 
public class FileController {
       
           @Autowired
           private FileService fileService;
       
           /**
            * 业务说明: 实现图片上传
            * URL: http://localhost:8091/file/upload
            * 类型: post
            * 参数: MultipartFile file 字节信息
            * 返回值: SysResult.success()
            * 问题思考:
            *      1.完成图片类型校验 jpg|png|gif....
            *      2.防止恶意程序    a.exe.jpg
            *      3.将图片分目录存储
            *             3.1.按照类型分   理论可以但是得多分配几个
            *             3.2.按照时间划分. yyyy/MM/dd
            *      4.自定义文件名称. 利用UUID充当图片名称.
            */
           @PostMapping("/upload")
           public SysResult upload(MultipartFile file) throws IOException {
       
               ImageVO imageVO = fileService.upload(file);
               if(imageVO == null){
                   return SysResult.fail();
               }
               return SysResult.success(imageVO);
           }
       
       
           /**
            * 业务说明: 实现图片上传
            * URL: http://localhost:8091/file/upload
            * 类型: post
            * 参数:  file 字节信息
            * 返回值: SysResult.success()
            * 扩展:
            *      一般情况下:
            *          一般前端向后端服务器发送字节信息.由外到内实现数据传输.
            *      采用输入流信息. InputStream file
            *          使用字节流的弊端: 1.必须手动关闭, 2.代码操作繁琐
            *          底层代码的实现.
            *       SpringMVC高级API  MultipartFile 专门处理IO流操作
            *  文件上传步骤:
            *        1.获取文件名称
            *        2.准备文件上传的目录
            *        3.判断目录是否存在  存在目录: 实现上传  没有目录:创建目录
            *        4.利用工具API方法,实现文件上传.
            *  注意事项: MultipartFile 默认支持1M的数据
            */
            /*@PostMapping("/upload")
            public SysResult upload(MultipartFile file) throws IOException {
                //1.获取文件名称
                String fileName = file.getOriginalFilename();
                //2.准备磁盘地址
                String dirPath = "E:/project3/images/";
                //3.将这个文件目录 封装为File对象
                File dirFile = new File(dirPath);
                //4.判断对象是否存在
                if(!dirFile.exists()){
                   //如果文件目录不存在,则创建目录
                    dirFile.mkdirs(); //表示多级目录上传.
                }
                //5.封装文件全路径 E:xxx/xxx/a.jpg
                String path = dirPath + fileName;
                File allFile = new File(path);
                //6.实现文件上传 将IO流按照指定的对象格式进行输出.
                file.transferTo(allFile);
                return SysResult.success();
            }*/ }

编辑FileService

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";

    //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));

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

        return null;
    }
}

文件删除操作

文件删除JS
在这里插入图片描述
文件删除业务接口

  • 请求路径: http://localhost:8091/file/deleteFile

  • 请求类型: delete

  • 请求参数:
    在这里插入图片描述

  • 返回值结果:
    在这里插入图片描述

编辑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

属性动态赋值

在这里插入图片描述

正则表达式(复习)

正则表达式说明

正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。
总结: 正则表达式就是一种特殊格式的字符串.校验文本信息的.

匹配不确定次数

在这里插入图片描述

匹配固定次数

在这里插入图片描述

匹配取值区间

在这里插入图片描述

分组匹配

(jpg|png|gif)
在这里插入图片描述

正则案例练习

  • 要求匹配电话号码 11位 开头都是1
    正则表达式: 1[3-9][0-9]{9}
  • 要求匹配邮箱 xxxx@qq.com
    正则表达式:
^[a-zA-Z0-9-_]+@[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+$
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值