spingmvc+vue+axios 文件(图片)异步上传,商城增删改查案例

本文详细介绍了如何使用Vue.js前端框架与Mybatis后端技术实现文件上传功能,包括项目配置、数据访问层、业务层及控制器的具体实现。同时,文章展示了增删改查功能的实现,并附上了完整的代码示例。

最近Vue使用体验感十分不错,深刻体会到这款前端框架的强大,在此也向尤雨溪前辈致敬。

其实Vue这个东西在数据渲染方面就像Java的EL表达式,如果用JQuery获得数据我们需要拼接节点的方式将数据渲染到页面上,如<td>data</td>。但是Vue不会对节点直接操作,而是使用{{data}}这种方式直接渲染到页面,如果有做过微信小程序开发的会知道小程序也是这种渲染方式。

当然,标题说的是文件上传,但是下面的案例也有涉及到其他知识点(Mybatis、Vue的其他知识点)。如果只想看文件上传的,就直接去增加或者修改功能模块看。案例有增删改查,下面的展示也是分功能展示。

0.先看一下整体效果以及项目结构

环境:

操作系统:win7

JDK版本:jdk8

开发工具:idea

 

1.数据库表、实体类说明

//没有实现此接口的类将不能使它们的任意状态被序列化或反序列化,简单说就是不让数据乱掉 
public class Item implements Serializable {
    private Integer id;//商品ID
    private String name;//商品名称
    private float price;//商品价格
    private String detail;//商品备注
    private String pic;//图片路径,图片不直接放在数据库,存放在本机路
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date createtime;//创建时间,备注不是时间格式,是为了接收时间参数

//数据库就一张表,字段如上,在此不展示
get/set...
}
//查询类,方便我们查询
public class QueryVo implements Serializable {
    private String name;//商品名
    private Float minPrice;//商品最低价格
    private Float maxPrice;//商品最低价格

//get/set..
}

 

2.配置

.c3p0.properties

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/itemdb?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root

SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <package name="com.wy.item.entity"/>
    </typeAliases>
</configuration>

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <context:component-scan base-package="com.wy.item"/>
    <context:property-placeholder location="classpath:c3p0.properties"/>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:SqlMapConfig.xml"/>
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.wy.item.mapper"/>
    </bean>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

 springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.wy.item.contoller"/>
    <mvc:annotation-driven/>
    <mvc:default-servlet-handler/>
<!-- 配置文件上传解析器 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="10485760000"/>
        <property name="maxInMemorySize" value="4096"/>
        <property name="defaultEncoding" value="UTF-8"></property>
    </bean>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="WEB-INF/content/"
          p:suffix=".jsp"/>
</beans>

3.数据访问层

public interface ItemMapper {
    List<Item> getItemList(QueryVo queryVo);
    void updateItem(Item item);
    void addItem(Item item);
    void deleteItem(Integer id);
}
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wy.item.mapper.ItemMapper">
    <select id="getItemList" parameterType="queryVo" resultType="item">
        select
        id,
        name,
        price,
        detail,
        pic,
        createtime
        from items
        where 1=1
        <if test="name != null and name != ''">
            and name = #{name}
        </if>
        <if test="minPrice != null and minPrice != '' and maxPrice != null and maxPrice != ''">
            and price between #{minPrice} and #{maxPrice}
        </if>
<!-- 如果没有任何条件则全查,这样减少代码冗余-->
    </select>

    <update id="updateItem" parameterType="item">
        update items
        set name = #{name},
            price = #{price},
            detail = #{detail},
            pic = #{pic},
            createtime = #{createtime}
          where id = #{id}
    </update>

    <insert id="addItem" parameterType="item">
        insert into items(
            name,
            price,
            detail,
            pic,
            createtime
          )
        values(
            #{name},
            #{price},
            #{detail},
            #{pic},
            #{createtime}
          )
    </insert>

    <delete id="deleteItem" parameterType="Integer">
        delete from items
        where id = #{id}
    </delete>
</mapper>

4.业务层略,只是调用数据访问层方法

5.Contoller,下面结合页面分别介绍增删改查(代码会展示部分,最后面会给出Contoller和页面的完整代码)

a.查询

@RestController//本类方法都会默认使用@ResponseBody注解
public class ItemContoller {
    @Autowired
    private ItemService itemService;

    @RequestMapping("/getItemList")
    public List<Item> getItemList(@RequestBody QueryVo queryVo) {//前端传来Json格式数据    
        System.out.println("ItemContoller.getItemList");
        System.out.println(queryVo);
        List<Item> itemList = itemService.getItemList(queryVo);
        return itemList;//以Json格式响应给客户端
    }
}
 created() {
            var _this = this;//注1
            axios.post('getItemList', {
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (response) {
                console.log(response.data)
                _this.itemList = response.data
            })
        },

可以先去最后面看看完整代码,这样再看部分的就会清楚很多。

created()是Vue的钩子函数,也就是生命周期函数。这个函数是Vue自动调用,不需要我们手动调用,调用时机则在Vue创建完成时。所以把获取所有数据的异步请求写在该函数中,目的就是一进页面通过异步请求加载所有数据。

注1:设置一个临时变量,把当前的Vue对象存起来。因为在下面的then(function (response))出现了闭包,如果还用this,则是属于axios

最后,页面渲染超简单

<p><b>编号:</b>{{item.id}}</p>
<p><b>名称:</b>{{item.name}}</p>
<p><b>价格:</b>{{item.price}}</p>
<p><b>备注:</b>{{item.detail}}</p>

b.删除

@RequestMapping("/deleteItem")
    public String deleteItem(@RequestBody Map<String, Integer> id) {
        System.out.println(id);
        itemService.deleteItem(id.get("id"));
        return "ok";
    }

参数用Map接收是有原因的,前端以Json格式发送数据,在使用Jackson的情况下。直接使用Integer接收参数会抛出反序列化异常。

 

deleteItem: function (index, id) {
                console.log(index)
                console.log(id)
                var _this = this
                var dataId = {
                    "id": id
                }
                console.log(dataId.id)
                axios.post('deleteItem', dataId)
                    .then(function (response) {
                        if (response.data == 'ok') {
                            alert('删除成功!')
                            _this.itemList.splice(index, 1)
                        }
                    })
            },

c.添加

public class PicUtil {
    public static String upPic(MultipartFile file) throws IOException {
        if (file.getSize() > 0) {
            String orName = file.getOriginalFilename();
            String extName = orName.substring(orName.lastIndexOf("."));
            String newName = new Date().getTime() + extName;
            File file2 = new File("E:/imgs/" + newName);
            file.transferTo(file2);
            return newName;
        } else {
            System.out.println("没有选择文件");
        }
        return null;
    }
}

 上传文件这一套处理是要反复用的,所以把他封装成一个工具类。

 @RequestMapping("/addItem")
    public String addItem(MultipartFile file, Item item) throws IOException {
        System.out.println(item);
        item.setCreatetime(new Date());
        String fileName = PicUtil.upPic(file);
        System.out.println(fileName);
        item.setPic(fileName);
        itemService.addItem(item);
        return "ok";
    }

 这里参数接收不能再用@RequestBody,前端传过来的不再是Json格式的数据。

后端收到一个请求,DispatcherServlet的checkMultipart()方法会调用MultipartResolver 的 isMultipart() 方法判断请求中是否包含文件。如果存在文件,则调用 MultipartResolver 的 resolveMultipart() 方法对请求的数据进行解析,然后将文件数据解析成 MultipartFile 并封装在 MultipartHttpServletRequest (继承了 HttpServletRequest) 对象中,最后传递给 Controller。

 addItem: function () {
            var _this = this
			var formData = new FormData($('#addForm')[0])
			axios.post('addItem', formData, {
                headers: {
                        'Content-Type': 'multipart/form-data'
                    }
                }).then(function (response) {
                    if (response.data == 'ok') {
                        alert('添加成功!')
                        _this.queryItem()
                    }
                })
            }

 d.修改

  @RequestMapping("/updateItem")
    public String updateItem(MultipartFile file, Item item) throws IOException {
        String fileName = PicUtil.upPic(file);
        if (fileName != null && !fileName.trim().isEmpty()) {
            item.setPic(fileName);
        }
        itemService.updateItem(item);
        return "ok";
    }

判断其实就是判断有没有修改图片,如果有修改则把新的图片路径set进实体类最后存入数据库,没有则使用原来路径

在前端表单中是这样实现的

<label class="col-sm-2 control-label">图片</label>
<input type="hidden" name="pic" :value="backData.pic"/>
<div class="col-sm-10">
    <img style="width: 180px;height: 180px" :src="'/imgs/'+backData.pic">  
    <input type="file" class="form-control" name="file"/>
</div>

设置隐藏域,把原来的路径传回去

 updateItem: function () {
                var _this = this
                var formData = new FormData($("#updateForm")[0])
                console.log(formData)
                axios.post('updateItem', formData, {
                    headers: {
                        'Content-Type': 'multipart/form-data'
                    }
                }).then(function (response) {
                    if (response.data == 'ok') {
                        alert('更新成功!')
                        _this.queryItem()
                    }
                })
            },

再说一下数据修改功能数据回显到模态框的问题

用Vue做十分简单

当我们点击修改按钮时先调用一个方法

 backShow: function (index) {
                this.backData = this.itemList[index]
                console.log(this.backData)

            },

通过下标从原来的数据数组中取出要修改的商品数据然后渲染到修改模态框

5.总结

到这里功能也就全部展示完了,下面贴一下页面和Contoller全部代码

@RestController
public class ItemContoller {

    @Autowired
    private ItemService itemService;

    @RequestMapping("/getItemList")
    public List<Item> getItemList(@RequestBody QueryVo queryVo) {
        System.out.println("ItemContoller.getItemList");
        System.out.println(queryVo);
        List<Item> itemList = itemService.getItemList(queryVo);
        return itemList;
    }

    @RequestMapping("/deleteItem")
    public String deleteItem(@RequestBody Map<String, Integer> id) {
        System.out.println(id);
        itemService.deleteItem(id.get("id"));
        return "ok";
    }

    @RequestMapping("/addItem")
    public String addItem(MultipartFile file, Item item) throws IOException {
        System.out.println(item);
        item.setCreatetime(new Date());
        String fileName = PicUtil.upPic(file);
        System.out.println(fileName);
        item.setPic(fileName);
        itemService.addItem(item);
        return "ok";
    }

    @RequestMapping("/updateItem")
    public String updateItem(MultipartFile file, Item item) throws IOException {
        String fileName = PicUtil.upPic(file);
        if (fileName != null && !fileName.trim().isEmpty()) {
            item.setPic(fileName);
        }
        itemService.updateItem(item);
        return "ok";
    }
}
<!DOCTYPE html>
<html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml">

<head>
    <meta charset="UTF-8">
    <title>商品列表</title>
    <script src="static/jquery-2.1.4.min.js" type="text/javascript" charset="utf-8"></script>
    <script src="static/jquery.serializejson.min.js" type="text/javascript" charset="utf-8"></script>
    <script src="static/vue.js" type="text/javascript" charset="utf-8"></script>
    <script src="static/axios.min.js" type="text/javascript" charset="utf-8"></script>
    <script src="static/bootstrap.min.js" type="text/javascript" charset="utf-8"></script>
    <link rel="stylesheet" type="text/css" href="static/bootstrap.css"/>
    <style type="text/css">
        body {
            font-size: 16px;
        }
    </style>
</head>

<body>
<div id="app">
    <div class="panel panel-default">
        <div class="panel-heading text-center">
            <h3>商品</h3>
        </div>
        <div class="panel panel-body">

            <div class="container-fluid">
                <form id="queryForm" class="form-inline">
                    <div class="form-group">
                        <label class="control-label">商品名称:</label>
                        <input type="text" class="form-control" name="name"/>
                    </div>
                    <div class="form-group">
                        <label class="control-label">价格区间:</label>
                        <input type="text" class="form-control" name="minPrice" placeholder="最低价格"/>
                    </div>
                    ——
                    <div class="form-group">
                        <input type="text" class="form-control" name="maxPrice" placeholder="最高价格"/>
                    </div>
                    <div class="form-group">
                        <input @click="queryItem" type="button" class="btn btn-primary" value="查询"/>
                    </div>
                    <div class="form-group">
                        <button type="button" class="btn btn-success"data-toggle="modal" data-target="#myModal2">添加商品
                        </button>
                    </div>
                </form>
                <div class="row">
                    <div class="col-md-4 text-left" v-for="(item, index) in itemList">
                        <img class="center-block" style="width: 300px;height: 300px"
                             v-if="item.pic != null && item.pic != ''" :src="'/imgs/'+item.pic">
                        <div class="center-block" style="width: 300px;height: 300px">
                            <p><b>编号:</b>{{item.id}}</p>
                            <p><b>名称:</b>{{item.name}}</p>
                            <p><b>价格:</b>{{item.price}}</p>
                            <p><b>备注:</b>{{item.detail}}</p>
                            <p>
                                <button @click="backShow(index)" type="button" class="btn btn-info" data-toggle="modal"
                                        data-target="#myModal">修改
                                </button>
                                <button @click="deleteItem(index, item.id)" class="btn btn-danger">删除</button>
                            </p>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </div>
    <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                            aria-hidden="true">&times;</span></button>
                    <h4 class="modal-title" id="myModalLabel">修改商品</h4>
                </div>
                <div class="modal-body">
                    <form id="updateForm" class="form-horizontal">
                        <div class="form-group">
                            <label class="col-sm-2 control-label">编号</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" name="id" v-model="backData.id"
                                       readonly="readonly"/>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-2 control-label">名称</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" name="name" v-model="backData.name"/>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-2 control-label">价格</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" name="price" v-model="backData.price"/>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-2 control-label">备注</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" name="detail" v-model="backData.detail"/>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-2 control-label">图片</label>
                            <input type="hidden" name="pic" :value="backData.pic"/>
                            <div class="col-sm-10">
                                <div>
                                    <img style="width: 180px;height: 180px" :src="'/imgs/'+backData.pic">
                                </div>
                                <input type="file" class="form-control" name="file"/>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-2 control-label">创建时间</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" name="createtime"
                                       :value="backData.createtime | formatTime('YMDHMS')" readonly="readonly"/>
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-sm-offset-2 col-sm-10">
                                <button @click="updateItem" type="button" class="btn btn-primary" data-dismiss="modal">
                                    提交
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
    <div class="modal fade" id="myModal2" tabindex="-1" role="dialog" aria-labelledby="myModalLabel2">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                            aria-hidden="true">&times;</span></button>
                    <h4 class="modal-title">添加商品</h4>
                </div>
                <div class="modal-body">
                    <form id="addForm" class="form-horizontal">
                        <div class="form-group">
                            <label class="col-sm-2 control-label">名称</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" name="name"/>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-2 control-label">价格</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" name="price"/>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-2 control-label">备注</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" name="detail"/>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-2 control-label">图片</label>
                            <div class="col-sm-10">
                                <input type="file" class="form-control" name="file"/>
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-sm-offset-2 col-sm-10">
                                <button @click="addItem" type="button" class="btn btn-primary" data-dismiss="modal">
                                   	提交
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
<script type="text/javascript">
    function addZero(val) {
        if (val < 10) {
            return "0" + val;
        } else {
            return val;
        }
    };
    Vue.filter("formatTime", function (value, type) {
        var dataTime = "";
        var data = new Date();
        data.setTime(value);
        var year = data.getFullYear();
        var month = addZero(data.getMonth() + 1);
        var day = addZero(data.getDate());
        var hour = addZero(data.getHours());
        var minute = addZero(data.getMinutes());
        var second = addZero(data.getSeconds());
        if (type == "YMD") {
            dataTime = year + "-" + month + "-" + day;
        } else if (type == "YMDHMS") {
            dataTime = year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second;
        } else if (type == "HMS") {
            dataTime = hour + ":" + minute + ":" + second;
        } else if (type == "YM") {
            dataTime = year + "-" + month;

        }
        return dataTime; //将格式化后的字符串输出到前端显示
    });
    var app = new Vue({
        el: "#app",
        data: {
            itemList: [],
            backData: {}
        },
        created() {
            var _this = this;
            axios.post('getItemList', {
                headers: {
                    'Content-Type': 'application/json'
                }
            }).then(function (response) {
                console.log(response.data)
                _this.itemList = response.data
            })
        },
        methods: {
            queryItem: function () {
                var _this = this
                var dataForm = $('#queryForm').serializeJSON()
                console.log(dataForm)
                axios.post('getItemList', dataForm)
                    .then(function (response) {
                        console.log(22222222222222222222222222222222)
                        console.log(response.data)
                        _this.itemList = response.data
                    })
            },
            deleteItem: function (index, id) {
                console.log(index)
                console.log(id)
                var _this = this
                var dataId = {
                    "id": id
                }
                console.log(dataId.id)
                axios.post('deleteItem', dataId)
                    .then(function (response) {
                        if (response.data == 'ok') {
                            alert('删除成功!')
                            _this.itemList.splice(index, 1)
                        }
                    })
            },
            backShow: function (index) {
                this.backData = this.itemList[index]
                console.log(this.backData)

            },
            updateItem: function () {
                var _this = this
                var formData = new FormData($("#updateForm")[0])
                console.log(formData)
                axios.post('updateItem', formData, {
                    headers: {
                        'Content-Type': 'multipart/form-data'
                    }
                }).then(function (response) {
                    if (response.data == 'ok') {
                        alert('更新成功!')
                        _this.queryItem()
                    }
                })
            },
            addItem: function () {
            	var _this = this
				var formData = new FormData($('#addForm')[0])
				axios.post('addItem', formData, {
                    headers: {
                        'Content-Type': 'multipart/form-data'
                    }
                }).then(function (response) {
                    if (response.data == 'ok') {
                        alert('添加成功!')
                        _this.queryItem()
                    }
                })
            }
        }
    })
</script>

</html>

项目还有很多地方需要完善,比如图片上传没有做验证,其使用Vue做图片验证比较简单,可以限制图片大小还有格式。

 

<think>我们正在分析一个基于多种技术栈构建的应用平台的整体架构设计。根据用户描述,系统使用了以下技术:后端:SpringBoot,SpringMVC,Shiro,MyBatis前端:Vue,Vuex,VueRouter,Axios,Element数据层:MySQL,Ehcache整体架构设计通常采用分层架构,包括表示层(前端)、业务逻辑层(后端)和数据访问层(数据层)。下面我们逐一解析各层使用的技术及其作用。###1.后端技术栈-**SpringBoot**:作为基础框架,提供快速配置和启动Spring应用的能力,简化了传统Spring应用的初始搭建和开发过程。-**SpringMVC**:作为Web层框架,处理HTTP请求和响应,实现MVC(Model-View-Controller)模式,负责请求的路由和视图解析(但在前后端分离架构中,后端通常只提供API,视图由前端负责)。-**Shiro**:安全框架,负责认证(Authentication)、授权(Authorization)、加密(Cryptography)和会话管理(SessionManagement)等安全功能。-**MyBatis**:持久层框架,用于操作数据库,通过XML或注解配置SQL映射,实现对象关系映射(ORM)。###2.前端技术栈-**Vue**:渐进式JavaScript框架,用于构建用户界面,采用组件化开发模式。-**Vuex**:状态管理库,用于集中管理Vue应用中多个组件共享的状态。-**VueRouter**:路由管理器,实现单页面应用(SPA)中的页面导航。-**Axios**:基于Promise的HTTP客户端,用于前端与后端API的异步通信。-**Element**:基于Vue的UI组件库,提供丰富的可复用组件,加速前端界面开发。###3.数据层技术-**MySQL**:关系型数据库管理系统,用于持久化存储业务数据。-**Ehcache**:纯Java的进程内缓存框架,用于缓存数据以提高系统性能,减少对数据库的访问压力。###系统架构工作流程1.**用户请求流程**:-用户通过浏览器访问前端应用(Vue构建的SPA)。-前端通过VueRouter解析路由,加载相应组件。-组件中通过Axios发送HTTP请求到后端API(SpringMVC控制器)。-后端控制器接收到请求后,调用服务层处理业务逻辑。-服务层可能通过MyBatis访问MySQL数据库,或使用Ehcache缓存数据。-服务层处理完成后,将结果返回给控制器,控制器再以JSON格式返回给前端。-前端接收到响应后,通过Vuex更新状态,并重新渲染组件。2.**安全控制流程**:-用户登录时,前端发送认证请求到后端。-后端Shiro进行身份认证(如验证用户名密码),成功后生成Token(或Session)并返回给前端。-后续请求中,前端携带Token(通常放在HTTPHeader中),后端Shiro进行权限校验(如访问控制、角色验证)。3.**数据缓存流程**:-当服务层需要查询数据时,先检查Ehcache中是否存在缓存数据。-如果存在,直接返回缓存数据;否则查询数据库,并将结果缓存到Ehcache中(设置合适的过期策略)。###架构特点-**前后端分离**:前后端通过API交互,职责清晰,便于独立开发和部署。-**分层设计**:各层技术分工明确,便于维护和扩展。-**模块化**:前端采用Vue组件化,后端通常采用模块化设计(如按功能划分模块)。-**安全性**:通过Shiro框架提供全面的安全控制。-**性能优化**:使用Ehcache减少数据库访问,提高响应速度。###可能的架构图```plaintext+-----------------++-----------------++-----------------++-----------------+|前端||后端||数据层||安全||(Vue,Vuex,|<----->|(SpringBoot,|<----->|(MySQL)||(Shiro)||VueRouter,|API|SpringMVC,||||||Axios,Element)||MyBatis)|<------|(Ehcache)|<------||+-----------------++-----------------++-----------------++-----------------+```###相关技术组合说明-**SpringBoot+MyBatis**:提供快速开发持久层的能力,MyBatis的SQL灵活性适合复杂查询。-**Vue全家桶(Vue+Vuex+VueRouter)**:构建现代化的单页面应用。-**Shiro集成**:与SpringBoot结合,通过注解或URL配置实现细粒度权限控制。###注意事项-在微服务架构中,上述技术栈可能用于单个微服务,而用户提到的平台可能是单体应用或微服务架构中的一个服务。根据引用[1]和[2],用户可能也关注微服务架构,但当前问题描述的技术栈更偏向于单体应用或一个独立的服务模块。###引用说明在解析中,我们参考了用户提供的引用内容,特别是关于技术栈的描述[^1][^2][^3],但主要依据用户本次问题中明确的技术栈进行分析。§§相关问题§§1.在前后端分离架构中,如何设计API接口以保证安全性和高效性?2.Shiro和SpringSecurity在安全控制方面有何区别?如何选择?3.如何优化MyBatis与Ehcache的集成以提高数据库访问性能?4.Vuex在大型前端应用中管理状态有哪些最佳实践?5.如何设计一个基于SpringBoot和Vue的权限管理系统(包括菜单、按钮权限)?</think>### 基于多技术栈的应用平台架构解析 该平台采用**前后端分离架构**,结合经典Java后端生态与现代化Vue前端框架,以下是分层解析: #### 一、后端架构层(SpringBoot + SpringMVC + Shiro + MyBatis) 1. **SpringBoot核心** - 提供自动配置、快速启动能力 - 整合各模块依赖(如Shiro安全框架、MyBatis ORM) - 管理服务生命周期:`@SpringBootApplication`启动嵌入式Tomcat 2. **SpringMVC请求处理** - 控制器层(`@RestController`)接收前端Axios请求 - 路由分发:`@RequestMapping`定义API端点 - 数据序列化:Jackson库转换JSON ↔ Java对象 3. **Shiro安全控制** - 认证流程:`Realm`实现用户密码验证 - 授权机制:`@RequiresRoles`控制接口访问权限 - 会话管理:分布式Session存储(常集成Redis) 4. **MyBatis数据持久化** - SQL映射:XML/注解定义`Mapper`接口 - 事务管理:`@Transactional`注解声明式事务 - 连接池:Druid监控SQL性能 #### 二、前端架构层(Vue + Vuex + Vue Router) 1. **Vue组件化开发** - 单文件组件(`.vue`)结构:`<template>`, `<script>`, `<style>` - Element UI组件库:表格/表单/弹窗等预制组件 2. **状态管理(Vuex)** ```javascript // store模块示例 const store = new Vuex.Store({ state: { userInfo: null }, mutations: { SET_USER(state, payload) { state.userInfo = payload } }, actions: { login({ commit }, credentials) { return axios.post('/api/login', credentials) .then(res => commit('SET_USER', res.data)) } } }) ``` 3. **路由控制(Vue Router)** - 动态路由:根据权限生成可访问路由表 - 导航守卫:`beforeEach`拦截未认证访问 #### 三、数据层(MySQL + Ehcache) 1. **MySQL存储** - 主从读写分离提升并发能力 - InnoDB引擎保障事务一致性 2. **Ehcache缓存优化** - 二级缓存:MyBatis集成减少数据库查询 - 缓存策略:`timeToLiveSeconds`控制过期时间 ```xml <!-- MyBatis配置示例 --> <cache type="org.mybatis.caches.ehcache.EhcacheCache"> <property name="timeToIdleSeconds" value="3600"/> </cache> ``` #### 四、系统交互流程 ```mermaid graph LR A[浏览器] --> B(Vue前端) B -->|Axios请求| C[SpringMVC控制器] C -->|Shiro鉴权| D[Service业务层] D -->|MyBatis调用| E[Ehcache] E -->|缓存未命中| F[MySQL数据库] F --> E --> D --> C --> B --> A ``` #### 架构优势 1. **前后端解耦**:独立开发部署,API契约定义交互 2. **安全可控**:Shiro RBAC模型实现细粒度权限 3. **性能优化**:Ehcache减少DB压力,Vue虚拟DOM提升渲染效率 4. **扩展性**:SpringBoot Starter机制支持模块化扩展 > 参考典型实现:RuoYi-Cloud采用类似架构实现分布式微服务[^1],若依平台展示SpringBoot+MyBatis+Vue的权限管理方案[^3]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值