商品后台管理

页面JS编辑

存储浮点数存在精度问题,所以价格我们会在数据库中*100来存储,在前端来处理

function submitForm(){
        //表单校验
        if(!$('#itemAddForm').form('validate')){
            $.messager.alert('提示','表单还未填写完成!');
            return ;
        }
        //转化价格单位,将元转化为分
        //$("#price").val(); 取值 $("#price").val(100);
        //eval() 专门做算数计算的 1+1 "1"+1
        $("#itemAddForm [name=price]").val(eval($("#itemAddForm [name=priceView]").val()) * 100);
        itemAddEditor.sync();//将输入的内容同步到多行文本中
        
        $.post("/item/save",$("#itemAddForm").serialize(), function(data){
            if(data.status == 200){
                $.messager.alert('提示','新增商品成功!');
            }else{
                $.messager.alert("提示","新增商品失败!");
            }
        });
    }

封装SysResult

说明:在jt-common中添加系统返回值VO对象

对于增删改操作,没有具体返回值的情况,我们统一返回结果,如果程序执行异常返回201状态码,成功才返回200状态码,前端可以根据状态码控制给用户的反馈

package com.jt.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

//作用: 指定系统返回值vo对象,与前端进行交互
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SysResult {
    private Integer status; //200成功 201失败
    private String msg; //服务器返回的提示信息
    private Object data; //服务器数据

    //1.编辑失败方法
    public static SysResult fail(){

        return new SysResult(201,"服务器调用失败",null);
    }

    //2.重载成功方法
    public static SysResult success(){

        return new SysResult(200,"服务器执行成功",null);
    }

    public static SysResult success(Object data){

        return new SysResult(200,"服务器执行成功",data);
    }

    public static SysResult success(String msg,Object data){

        return new SysResult(200,msg,data);
    }
}

全局异常处理机制说明

说明:在jt-common中 添加全局异常处理机制.
定义全局异常处理,无序我们每次try-catch

package com.jt.aop;

import com.jt.vo.SysResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice //定义全局异常处理
public class SystemException {

    //遇到运行时异常时方法执行.
    @ExceptionHandler({RuntimeException.class})
    public Object fail(Exception e){
        e.printStackTrace(); //输出异常信息.
        return SysResult.fail();
    }
}

自动填充功能

业务需求

例如更新时间/创建时间,每个业务操作时都需要更新相关数据,能否将数据进行优化,简化程序调用.
在这里插入图片描述

编辑BasePOJO 指定填充属性

@TableField(fill=FiedFill.INSERT)表示在所有插入操作时执行(具体执行的内容通过配置文件添加)
@TableField(fill=FiedFill.INSERT_UPDATE)表示插入和更新操作
在这里插入图片描述

编辑配置类

说明:在jt-common中 编辑配置类,实现自动填充功能.

package com.jt.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component  //将对象交给spring容器管理
public class MyMetaObjectHandler implements MetaObjectHandler {

    //完成入库操作自动赋值
    @Override
    public void insertFill(MetaObject metaObject) {
        Date date = new Date();
        this.setFieldValByName("created",date,metaObject);
        this.setFieldValByName("updated",date,metaObject);
    }

    //完成更新操作自动赋值
    @Override
    public void updateFill(MetaObject metaObject) {

        this.setFieldValByName("updated",new Date(),metaObject);
    }
}

商品修改操作

页面分析

{
        text:'编辑',
        iconCls:'icon-edit',
        handler:function(){
            //获取用户选中的数据
            var ids = getSelectionsIds();
            if(ids.length == 0){
                $.messager.alert('提示','必须选择一个商品才能编辑!');
                return ;
            }
            if(ids.indexOf(',') > 0){
                $.messager.alert('提示','只能选择一个商品!');
                return ;
            }
            //需要找到一个空的div之后展现窗口
            $("#itemEditWindow").window({
                onLoad :function(){
                    //回显数据
                    var data = $("#itemList").datagrid("getSelections")[0];
                    data.priceView = KindEditorUtil.formatPrice(data.price);
                    //将data的数据回显到修改页面中.
                    $("#itemeEditForm").form("load",data);
                    .....
                    }

实现修改页面分类信息回显

选中一个商品进行修改,数据从前端回显到更新页面,但是类目存储的时cid,所以,我们需要使用cid去数据库中查询,该类目的名称在回显过来
在这里插入图片描述

说明:可以通过商品分类Id,动态获取商品分类的名称.请求路径按照图中标识.在这里插入图片描述

编辑页面JS

在这里插入图片描述

商品修改的ajax

在这里插入图片描述

编辑ItemController

/**
     * 实现商品修改操作
     * 1.url地址: /item/update
     * 2.请求参数: form表单提交
     * 3.返回值:  SysResult对象
     */
    @RequestMapping("/update")
    public SysResult updateItem(Item item){

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

编辑ItemService

//一般更新操作都是根据主键更新
    //Sql: update tb_item set titel=#{xxxx},xx,x,x,x,x, where id=#{xxx}
    @Override
    public void updateItem(Item item) {
        //根据对象中不为null的元素充当set条件
        itemMapper.updateById(item);
    }

商品删除操作

页面url分析

分析前端传的数据ids为一个id串拼接的字符串
在这里插入图片描述
2.参数提交
在这里插入图片描述
3.页面JS分析
在这里插入图片描述

编辑ItemController

所以后端在接收参数时,需进行处理,或交给框架的DispatchServlet去处理,需要什么类型给什么类型

/**
     * 业务需求:  删除商品信息
     * 1.url地址:    /item/delete
     * 2.参数:  ids: 100,101,102    serlvet(request)   同名提交问题
     * 3.返回值: SysResult
     * springMVC规则: 如果传递的数据是由,号分隔的字符串则可以使用数组接收
     * */
    @RequestMapping("/delete")
    public SysResult deleteItems(Long... ids){
        //1.将ids字符串按照,号拆分
        itemService.deleteItems(ids);
        return SysResult.success();
    }

编辑ItemService

使用MP的方法时,需要使用Arrays工具的asList方法将数组转成集合

//批量删除操作
    @Override
    public void deleteItems(Long[] ids) {
        //List<Long> longList = Arrays.asList(ids);
        //itemMapper.deleteBatchIds(longList);

        //手动的删除数据
        itemMapper.deleteItems(ids);

    }

编辑ItemMapper

xml中处理数据牵扯到Mybatis的规则,xml中有详细说明,或者在Mapper中给参数加注解@Param(参数名)
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jt.mapper.ItemMapper">

    <!--
        Mybatis数据传参的原理:
            规则: Mybatis只能接收单值传参!!!
                 如果有多个数据需要传值,则需要将多值封装为单值
            方式:
                1.利用对象传参
                2.利用数组传参
                3.利用Map集合传参
            集合的写法:
                数据类型是数组          collection="array"
                数据类型是list集合    collection="list"
                数据类型是Map集合     collection="map的key"

        ids=100,101,102
        collection: 获取传递集合的key
        open="集合遍历前缀"
        close="集合遍历后缀"
        separator="分隔符"
        item="当前遍历的对象"
    -->
    <delete id="deleteItems"  >
        DELETE FROM tb_item WHERE id in (
         <foreach collection="array"  item="id" separator=",">
             #{id}
         </foreach>
        )
        
    </delete>

</mapper>

实现商品上架/下架操作

业务需求

说明:如果修改商品的状态信息. 上架=1 下架=2. 只需要修改数据库记录即可.
在这里插入图片描述

url分析:
http://localhost:8091/item/instock 下架操作 status=2
http://localhost:8091/item/reshelf 上架操作 status=1

需求: 能否利用一个方法实现上架/下架操作???
http://localhost:8091/item/updateStatus/2 下架操作 status=2
http://localhost:8091/item/updateStatus/1 上架操作 status=1

修改页面JS

说明:将页面上架/下架操作,按照上述分析进行修改.
在这里插入图片描述

编辑ItemController

/**
     * 业务: 实现商品的上架/下架
     * url地址: /item/updateStatus/2
     * 参数:  状态码信息/ids
     * 返回值: SysResult对象
     * */
    @RequestMapping("/updateStatus/{status}")
    public SysResult updateStatus(@PathVariable Integer status,Long... ids){

        itemService.updateStatus(ids,status);
        return SysResult.success();
    }

编辑ItemService

修改页面状态,通过状态吗控制,传参的时候,区分出来,下架传2,上架传1,后端可以直接修改状态码

//作业:sql手动完成
    //参数说明:  entity:修改数据的值   updateWrapper
    @Override
    public void updateStatus(Long[] ids, Integer status) {
        Item item = new Item();
        item.setStatus(status);
        //where id in (1,2,3,4)
        UpdateWrapper<Item> updateWrapper = new UpdateWrapper<>();
        updateWrapper.in("id",Arrays.asList(ids));
        itemMapper.update(item,updateWrapper);
    }

京淘后台表设计

为了给数据库减压,我们将商品的详细信息单独成表,当点击某一个商品时再从数据库中查询
在这里插入图片描述

准备POJO对象

商品描述信息的主键为该商品的主键
在这里插入图片描述

富文本编辑器介绍

KindEditor是一套开源的HTML可视化编辑器,主要用于让用户在网站上获得所见即所得编辑效果,兼容IE、Firefox、Chrome、Safari、Opera等主流浏览器。

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link href="/js/kindeditor-4.1.10/themes/default/default.css" type="text/css" rel="stylesheet">
<script type="text/javascript" charset="utf-8" src="/js/kindeditor-4.1.10/kindeditor-all-min.js"></script>
<script type="text/javascript" charset="utf-8" src="/js/kindeditor-4.1.10/lang/zh_CN.js"></script>
<script type="text/javascript" charset="utf-8" src="/js/jquery-easyui-1.4.1/jquery.min.js"></script>

<script type="text/javascript">
    $(function(){
        KindEditor.ready(function(){
            KindEditor.create("#editor")
        })
    })
</script>
</head>
<body>
<h1>富文本编辑器</h1>
<textarea style="width:700px;height:350px" id="editor"></textarea>
</body>
</html>

在这里插入图片描述

重构商品新增操作

新增时传参会增加itemDesc信息,所以需要分别插入两张表中,需要考虑事务问题,开启事务管理 @Transactional
另一个问题,在新增时,我们的商品主键是在数据库中自增的,所以我们才插入详细信息时,没有id值,此时需要考虑插入数据库中数据自增主键的回传问题。
这里MP框架自动开启,我们自己写XML时,需要手动开启

<!--实现主键自增的回显-->
<!--<insert id="" keyProperty="id" keyColumn="id" useGeneratedKeys="true"></insert>-->

编辑ItemController

@RequestMapping("/save")
    public SysResult saveItem(Item item, ItemDesc itemDesc){

        itemService.saveItem(item,itemDesc);
        return SysResult.success();
        /*try {
            itemService.saveItem(item);
            return SysResult.success();
        }catch (Exception e){
            e.printStackTrace();
            return SysResult.fail();
        }*/
    }

编辑ItemService

//xml文件配置 keyProperty="id" keyColumn="id" useGeneratedKeys="true"
    @Override
    @Transactional    //控制事务
    public void saveItem(Item item, ItemDesc itemDesc) {
        //1.入库商品信息
        item.setStatus(1);    //默认是正常状态
        itemMapper.insert(item); //执行数据库入库操作,动态生成ID
        //如何实现主键自增的回显功能? 可以通过标签的配置实现,但是MP已经实现该功能
        //2.入库详情信息  如何保证item与itemDesc主键信息一致?
        itemDesc.setItemId(item.getId());
        itemDescMapper.insert(itemDesc);
    }

商品详情回显

页面分析

前面只回显了商品信息,商品详细信息是单独发送的AJAX请求

$.getJSON('/item/query/item/desc/'+data.id,function(_data){
                        if(_data.status == 200){
                            itemEditEditor.html(_data.data.itemDesc);
                        }
                    });

编辑ItemController

/**
     * 需求: 根据商品Id,查询商品的详情信息.
     * url地址: http://localhost:8091/item/query/item/desc/1474392019
     * 参数:    商品Id号
     * 返回值:  SysResult对象
     */
    @RequestMapping("/query/item/desc/{itemId}")
    public SysResult findItemDescById(@PathVariable Long itemId){

        ItemDesc itemDesc = itemService.findItemDescById(itemId);
        return SysResult.success(itemDesc);
    }

编辑ItemService

@Override
    public ItemDesc findItemDescById(Long itemId) {

        return itemDescMapper.selectById(itemId);
    }

页面效果展现

在这里插入图片描述

重构商品修改

编辑ItemController

/**
     * 实现商品修改操作
     * 1.url地址: /item/update
     * 2.请求参数: form表单提交
     * 3.返回值:  SysResult对象
     */
    @RequestMapping("/update")
    public SysResult updateItem(Item item,ItemDesc itemDesc){

        itemService.updateItem(item,itemDesc);
        return SysResult.success();
    }

编辑ItemService

//一般更新操作都是根据主键更新
    //Sql: update tb_item set titel=#{xxxx},xx,x,x,x,x, where id=#{xxx}
    @Override
    @Transactional
    public void updateItem(Item item, ItemDesc itemDesc) {
        //根据对象中不为null的元素充当set条件
        itemMapper.updateById(item);
        itemDesc.setItemId(item.getId());
        itemDescMapper.updateById(itemDesc);
    }

重构商品删除

编辑ItemService

//批量删除操作
    @Override
    @Transactional
    public void deleteItems(Long[] ids) {
        List<Long> longList = Arrays.asList(ids);
        //itemMapper.deleteBatchIds(longList);

        //手动的删除数据
        itemMapper.deleteItems(ids);
        itemDescMapper.deleteBatchIds(longList);
    }

实现文件上传操作

入门案例

编辑页面

默认格式在我们进行图片或视频上传时,开启flash文件传输
enctype="multipart/form-data"(默认格式)

<body>
    <h1>实现文件长传</h1>
    <!--enctype="开启多媒体标签"  -->
    <form action="http://localhost:8091/file" method="post" 
    enctype="multipart/form-data">
        <input name="fileImage" type="file" />
        <input type="submit" value="提交"/>
    </form>
</body>

编辑FileController

接收参数时,使用输入流(inputstream),此处使用的是API中封装的流MultipartFile,用来获取对象的信息

@RestController
public class FileController {

    /**
     * url地址:  http://localhost:8091/file
     * 步骤:
     *      1.获取图片的名称
     *      2.准备文件目录
     *      3.拼接文件上传的路径
     *      4.实现文件上传.
     *
     * @param fileImage
     * @return
     */
    @RequestMapping("/file")
    public String file(MultipartFile fileImage) throws IOException {
        //1.获取图片名称
        String name = fileImage.getOriginalFilename();
        //2.准备文件上传目录
        String dir = "D:/JT-SOFT/images";
        //3.利用对象封装路径
        File dirFile = new File(dir);
        if(!dirFile.exists()){
            //如果不存在,则应该创建目录
            dirFile.mkdirs();  //创建多级目录
        }
        //4.实现文件上传
        File file = new File(dir+"/" +name);
        fileImage.transferTo(file);
        return "操作成功!!!";
    }
}

封装文件上传VO对象-imageVO

实际我们在页面中,装载这些属性需封装VO对象

{"error":0,"url":"图片的保存路径","width":图片的宽度,"height":图片的高度}
package com.jt.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ImageVO {
    // {"error":0,"url":"图片的保存路径","width":图片的宽度,"height":图片的高度}
    private Integer error;  //错误信息   0 正常  1 失败
    private String  url;    //图片网址
    private Integer width;  //宽度
    private Integer height; //高度

    //准备API 简化用户操作
    public static ImageVO fail(){

        return new ImageVO(1, null, null, null);
    }

    public static ImageVO success(String url,Integer width,Integer height){

        return new ImageVO(0,url, width, height);
    }

}

实现文件上传

页面url分析

在这里插入图片描述
2.获取参数名称
在这里插入图片描述

辑FileController

 @Autowired
    private FileService fileService;
    /**
     * 业务需求: 实现文件上传
     *  1.url地址: http://localhost:8091/pic/upload?dir=image
     *  2.请求参数: uploadFile
     *  3.返回值结果: ImageVO
     */
    @RequestMapping("/pic/upload")
    public ImageVO upload(MultipartFile uploadFile) throws IOException {

        return fileService.upload(uploadFile);
    }

编辑FileService

@Service
public class FileServiceImpl implements FileService {
        //定义文件存储的根目录
       private String fileLocalDir="D:/workspace4_idea/images";
       private static Set<String> typeSet=new HashSet<>();
       static {
           typeSet.add(".jpg");
           typeSet.add(".png");
           typeSet.add(".gif");
       }
    /**
     * 注意事项:
     *         1.校验是否为图片的类型     xxx.jpg|png|jpeg|git.....
     *         2.校验是否为恶意程序      像素检测 宽度|高度
     *         3.采用分目录方式进行数据的存储     1.hash方式  2.时间单位 yyyy/MM/dd/
     *         4.防止文件重名。。。      UUID.jpg
     * @param uploadFile
     * @return
     */
    @Override
    public ImageVo upload(MultipartFile uploadFile) {
        //1.获取图片的名称  a.jpg/A.jpg(操作系统导致)
        String filename = uploadFile.getOriginalFilename(); //123.jpg
        filename = filename.toLowerCase(); //全部转小写
        //2.获取图片的类型
        int index= filename.lastIndexOf(".");
        String fileType = filename.substring(index);    //.jpg
        //3.判断是否为图片
        if (!typeSet.contains(fileType)){
            return ImageVo.fail();  //结束任务
        }
        //4.校验是否为恶意程序   图片 宽度和高度(ImageIO 获取图片对象的javax包下封装的API)
        try {
            BufferedImage bufferedImage = ImageIO.read(uploadFile.getInputStream());
            int width = bufferedImage.getWidth();
            int height = bufferedImage.getHeight();
            if (width==0||height==0){
                return ImageVo.fail();
            }
            //5.采用分目录方式进行数据的存储(以时间为维度截串 /yyyy/MM/dd/)
            String dateDir =
                    new SimpleDateFormat("/yyyy/MM/dd/")
                            .format(new Date());
            //将根目录与分目录拼接
            String realFileDir = fileLocalDir+dateDir;
            //判断文件目录是否存在
            File imageFileDir=new File(realFileDir);
            if(!imageFileDir.exists()){
                //动态生成文件目录
                imageFileDir.mkdirs();
            }
            //6.防止文件重名,动态生成文件名称 uuid.jpg(32位16进制数)
            String uuid = UUID.randomUUID().toString().replace("-", "");
            String realFileName=uuid+fileType;  //uuid.jpg
            //7.文件上传
            File realFile=new File(realFileDir+realFileName);
            uploadFile.transferTo(realFile);
            //如果程序一切正常(临时存放一个网络图片的地址)
            return ImageVo.success("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1604569062&di=770a47c5a4b63730c2285f79e73ff762&src=http://a0.att.hudong.com/18/56/14300000958002128488569856508.jpg",width,height);

        } catch (IOException e) {
            //将检查异常转化为运行时异常
            e.printStackTrace();
            //throw new RuntimeException(e);
            return ImageVo.fail();
        }
    }
}

页面效果展现

在这里插入图片描述

京淘后台优化

路径优化

前面写的图片上传,url存储到数据库是写死的,今天优化写活存储在本地磁盘时用的路径为:例:D:/workspace4_idea/images/2020/11/6/xxx.jpg
我们访问时只需要替换fileLocalDir:D:/workspace4_idea/images即可,换成urlPath:http://image.it.com即系的呢撇姐为http://image.it.com/2020/11/6/xxx.jpg。首先我们将路径的值交给配置文件,通过注解获取

编辑properties配置文件

在这里插入图片描述

编辑FileServiceImpl

动态为属性赋值.
在这里插入图片描述
我们需要在访问urlPath时,服务器自动切换去访问本地磁盘的文件实现商品图片回显


禾白少二
57 声望26 粉丝