Springboot+vue 实战 white-jotter 第一部分

一、登录功能

项目:white_jotter_demo + wj_demo_vue

http://localhost:8081/#/login 访问页面

参考:https://learner.blog.csdn.net/article/details/88955387

 

前端页面开发:

1、src\components   编写Login.vue,AppIndex.vue   登录页面、首页页面

2、设置反向代理

src\main.js中设置   axios

前端请求默认发送到url,全局注册,之后可在其他组件中通过 this.$axios 发送数据

3、配置页面路由

src\router\index.js   导入组件

4、设置跨域支持

让后端能够访问到前端的资源

config\index.js 中,找到 proxyTable 位置

 

后端开发:

1、User类

         接收前端发送的数据,一个js对象

2、Result类

         Result 类,一个响应码,是为了构造 response

       Login()方法 返回对象的类型

 3、LoginController

         对响应进行处理,业务逻辑,

 

整个流程:

前端 Login.vue 中,发送post请求,通过设置 发到了 http://localhost:8443/api/login

.post('/login', {
            username: this.loginForm.username,
            password: this.loginForm.password
          })

后端LoginController接收,@PostMapping(value = "api/login")  

注释还有@ResponseBody,@CrossOrigin,共三个

 

LoginController里有一个方法:public Result login(@RequestBody User requestUser)

接收request,返回 Result类对象

 

前端  接收后端的response

.then(successResponse => {
            if (successResponse.data.code === 200) {
              this.$router.replace({path: '/index'})
            }
          })

响应码一致,登录成功跳转

 

综上所述

整个登录过程:

前端:Login.vue,  AppIndex.vue,  router/index.js

配置文件:Main.js 配置了 反向代理axios,Config/index.js 配置了proxyTable     

 

后端:User.java,  Result.java,  LoginController.java

配置文件: application.properties 配置 server.port=8443

 

二、数据库验证登录功能

1、建表

2、后端代码

1、配置数据库:

pom.xml,配置依赖;application.properties  配置数据库

2、User类:

建立对数据库的映射

(1)添加User注释:

@Entity    
@Table(name = "user")
@JsonIgnoreProperties({"handler","hibernateLazyInitializer"})

【备注】@Entity 表示这是一个实体类;@Table(name=“user”) 表示对应的表名是 user;

为了简化对数据库的操作,我们使用了 Java Persistence API(JPA),@JsonIgnoreProperties({ “handler”,“hibernateLazyInitializer” })

因为是做前后端分离,而前后端数据交互用的是 json 格式。 那么 User 对象就会被转换为 json 数据。 而本项目使用 JPA 来做实体类的持久化,JPA 默认会使用 hibernate, 在 JPA 工作过程中,就会创造代理类来继承 User ,并添加 handler 和 hibernateLazyInitializer 这两个无须 json 化的属性,所以这里需要用 JsonIgnoreProperties 把这两个属性忽略掉。

(2)添加主键id注释:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")

@id和@GeneratedValue都是JPA的标准用法; IDENTITY:主键由数据库自动生成(主要是自动增长型)

3、UserDAO

Data Access Object(数据访问对象,DAO)即用来操作数据库的对象。这里我们通过继承 JpaRepository 的方式构建 DAO。

public interface UserDAO extends JpaRepository<User,Integer> {
    User findByUsername(String username);
    User getByUsernameAndPassword(String username,String password);
}

方法名是关键,由于使用了 JPA,无需手动构建 SQL 语句,而只需要按照规范提供方法的名字即可实现对数据库的增删改查。

两个方法,一个是通过用户名查询,一个是通过用户名及密码查询。

4、UserService

@Service
public class UserService {
    @Autowired
    UserDAO userDAO;

    public boolean isExist(String username) {
        User user = getByName(username);
        return null!=user;
    }

    public User getByName(String username) {
        return userDAO.findByUsername(username);
    }

    public User get(String username, String password){
        return userDAO.getByUsernameAndPassword(username, password);
    }

UserService对 UserDAO 进行了二次封装,一般来讲,我们在 DAO 中只定义基础的增删改查操作,而具体的操作,需要由 Service 来完成。

当然,由于我们做的操作原本就比较简单,所以这里看起来只是简单地重命名了一下,比如把 “通过用户名及密码查询并获得对象” 这种方法命名为 get

5、LoginController

添加一个属性:

@Autowired
    UserService userService;

login方法里修改判断:

User user = userService.get(username, requestUser.getPassword());
if (null == user) {
    return new Result(400);
} else {
    return new Result(200);
}

这种简单的三层架构(DAO + Service + Controller):

  • DAO 用于与数据库的直接交互,定义基础的增删改查操作;
  • Service 负责业务逻辑,跟功能相关的代码一般写在这里,编写、调用各种方法对 DAO 取得的数据进行操作;
  • Controller 负责数据交互,即接收前端发送的数据,通过调用 Service 获得处理后的数据并返回前端。

倾向于让 Controller 显得清凉一些

 

综上所述:建表、数据库配置文件、

4个类:User,UserDAO,UserService,LoginController

三、使用 Element前端开发

1、装element:  cnpm i element-ui –S

2、引入element:

完整引入、在main.js中添加三行代码 

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(ElementUI)

修改 Login.vue,把<template>里 最外层的 <div> 标签改为 <el-card>

3、使用Form组件

Element 的官方地址  https://element.eleme.cn/#/zh-CN/component/form

设计界面,修改 <template> 标签内的 html 和 <style> 标签内的 css

显示代码,复制 自己构建

login.vue中:

<el-form> 里面可以放置 <el-form-item>,然后里面再放置其它的内容,比如 <el-input>,<el-button> 

添加css样式

设置背景

在 <el-form> 标签的外又添加了一个父标签 <body>,id 设置为 poster,然后在 <style> 中添加内容

写一个 body 的样式,是为了覆盖掉浏览器(用户代理)的默认样式。

over

基本上 到这 就可以理解 项目的构成了。之后就是 业务功能 了。

基本模式:前端开发组件、后端开发控制器,调试功能。

四、登录拦截器与前端路由

这个登录页面其实没有用,别人直接输入首页的网址,就可以绕过登录页面。因此,我们还需要开发一个拦截器

主要包含以下内容: 一、前端路由的 hash 模式与 history 模式;二、history 模式下后端错误页面的配置;三、登录拦截的实现

1、前端路由

URL 里有一个 # 号,# 被称为“锚点”,改变 URL 却不请求后端

利用 AJAX,我们可以不重载页面就刷新数据  + # 号的特性  = 可以在前端实现页面的整体变化,而不用每次都去请求后端

 

为了实现前端路由,我们可以监听 # 号后面内容的变化(hashChange),从而动态改变页面内容。URL 的 # 号后面的地址被称为 hash 。这种实现方式我们称之为 Hash 模式,是非常典型的前端路由方式。

另一种常用的方式被称为 History 模式,这种方式使用了 History APIHistory API 顾名思义就是针对历史记录的 API ,这种模式的原理是先把页面的状态保存到一个对象(state)里,当页面的 URL 变化时找到对应的对象,从而还原这个页面。(其实原本人家这个功能是为了方便浏览器前进后退的,不得不说程序员们的脑洞真大。使用了这种模式,就可以摆脱 # 号实现前端路由。)

2、使用 History 模式

router\index.js 中,加入 mode: 'history',

运行项目,访问不加 # 号的 http://localhost:8080/login ,成功加载页面。

3、后端登录拦截器

一个简单的登录拦截器的逻辑如下:

1.用户访问 URL,检测是否为登录页面,如果是登录页面则不拦截
2.如果用户访问的不是登录页面,检测用户是否已登录,如果未登录则跳转到登录页面

 

1、把前端打包后部署在后端

变成了前后端不分离项目了。

npm run build

将前端项目的 dist目录下生成的static 文件夹和 index.html 文件,复制到我们后端项目的 wj\src\main\resources\static 文件夹下。

2、ErrorConfig类

 package 名 error下,实现 ErrorPageRegistrar 接口

把默认的错误页面设置为 /index.html

@Component
public class ErrorConfig implements ErrorPageRegistrar {

    @Override
    public void registerErrorPages(ErrorPageRegistry registry) {
        ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html");
        registry.addErrorPages(error404Page);
    }

}

重新启动项目,访问 http://localhost:8443/login ,可以 成功进入登录页面。

3、LoginController

为了保存登录状态,我们可以把用户信息存在 Session 对象中(当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量不会丢失)。这样在访问别的页面时,可以通过判断是否存在用户变量来判断用户是否登录。

在 LoginController 中,方法参数多传了一个值 HttpSession session,登录成功处添加了一条代码 

session.setAttribute("user", user);

4、LoginInterceptor

package 名interceptor下,继承 HandlerInterceptor接口,重写实现 preHandle 方法。

这里的逻辑就是:判断request.getServletPath()是否是否是"/index",如果是,就判断是否登录(session.getAttribute("user") 是否为空),没有登录就重定向到"/login"response.sendRedirect(request.getContextPath() + "/login");

request_xxPath示意图

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        String servletPath = request.getServletPath();
        if(servletPath.startsWith("/index")){
            if(session.getAttribute("user") == null){
                System.out.println("Not Login!");
                response.sendRedirect(request.getContextPath() + "/login");
                return false;
            }
        }
        return true;
    }
}

5、MyWebConfigurer

package 名 config下,继承WebMvcConfigurer

@SpringBootConfiguration
public class MyWebConfigurer implements WebMvcConfigurer {
    @Bean
    public LoginInterceptor getLoginIntercepter(){
        return new LoginInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getLoginIntercepter()).addPathPatterns("/**").excludePathPatterns("/index.html");
    }
}

将写完了的拦截器,把它配置到项目中,最长的那条语句的作用 是对所有路径应用拦截器,除了 /index.html。

目前效果:

运行后端项目,访问 http://localhost:8443/index ,发现页面自动跳转到了 http://localhost:8443/login ,输入用户名和密码登录,跳转到 http://localhost:8443/index , 这时可以把浏览器标签关掉,再在一个新标签页输入 http://localhost:8443/index ,发现不会被拦截。

 

综上所述:

后端拦截器 步骤:

把前端打包后部署在后端、ErrorConfig类

LoginController、LoginInterceptor、MyWebConfigurer

问题:

现在是通过后端服务器的端口访问页面的,怎么样才能改成前端端口访问拦截?

他现在后端登录拦截器的作用:就是通过后端端口验证的,也就是就是在后端生效,而且这就是 前后端不分离项目了

难道后端拦截器 不能应用在 前后端分离项目 中吗?  token给出了答案??? 

 

详情见 https://learner.blog.csdn.net/article/details/89422585

对于前后端不分离项目 题主提出了  采用    Vuex 与前端登录拦截器

第十四部分 不知道是否可做出解释。


五、导航栏与图书管理页面

做 图书管理页面的前端部分

1、导航栏的实现

1、路由配置

App.vue 是所有组件的父组件。公共部分

新建Home.vue,components目录下,<template>中写入了一句 <router-view/>,也就是子页面(组件)显示的地方。

在一个组件中通过导入引用了其它组件,也可以称之为父子组件。但想要通过 <router-view/> 控制子组件的显示,则需要进行路由的相关配置。

修改router/index.js, routes里面: 将index配置为home的子组件。

routes: [
    {
      path: '/home',
      name: 'Home',
      component: Home,
      redirect: '/index',
      children: [
        {
          path: '/index',
          name: 'AppIndex',
          component: AppIndex,
          meta: {
            requireAuth: true
          }
        }
      ]
    }
]

注意我们并没有把首页的访问路径设置为 /home/index,仍然可以通过 /index 访问首页,这样配置其实是感受不到 /home 这个路径的存在的。之后再添加新的页面,可以直接在 children 中增添对应的内容。

2.使用 NavMenu 组件

在 components 文件夹里新建一个 common 文件夹,用来存储公共的组件,并在该文件夹新建一个组件 NavMenu.vue

<el-menu>里面添加

<el-menu-item v-for="(item,i) in navList" :key="i" :index="item.name"> {{ item.navItem }} </el-menu-item>

(1)在 <el-menu> 标签中我们开启了 router 模式 ,启用该模式时会在 激活导航时 以index 作为path 进行路由跳转

(2)通过 v-for 指令,把 navList 数组渲染为一组 <el-menu-item> 元素,也即导航栏的内容。

 

然后,把NavMenu组件放在 Home.vue 中

<template>
    <div>
      <nav-menu></nav-menu>
      <router-view/>
    </div>
</template>

<script>
  import NavMenu from './common/NavMenu'
  export default {
    name: 'Home',
    components:{NavMenu}
  }
</script>

2、图书管理页面

核心页面,我们先把它设计出来,以后再去实现具体的功能。

图书展示区域,分类导航栏,搜索栏,页码

1.LibraryIndex.vue 

图书页面的根组件

使用了 Element 提供的 Container 布局容器,把整个页面分为了侧边栏和主要区域两个部分

2、配置这个页面的路由,修改 router/index.js   把他放到home  的children: 里

2.SideMenu.vue

编写一个侧边栏组件SideMenu.vue,放在 /library 目录下

在 LibraryIndex.vue 中使用这个组件

<SideMenu></SideMenu>,   components:{SideMenu}

3.Books.vue

v-for 指令,之后可以使用动态渲染

el-tooltip Element 组件,用于展示鼠标悬停时的提示信息。

slot 插槽,及把标签中的内容插到父组件指定的地方,这里我们插入了 el-tooltip 的 content 中。上述文档中亦有描述。

封面图像标签中,我们使用了 :src="item.cover" 这种写法,: 其实是 v-bind: 的缩写,用于绑定把标签的属性与 data 中的值绑定起来。

搜索栏暂不理会

分页使用 el-pagination 组件,目前只是样式。

 

把 Books 组件放在 LibraryIndex.vue 中

<books class="books-area"></books>

 

访问 http://localhost:8081/library ,效果图预览

综上所述:

设置顶部导航栏

1、Home.vue,配置router/index.js; 2、NavMenu.vue,把NavMenu组件放在Home.vue 中

编写图书馆页面,设置侧边栏,放书

1、LibraryIndex.vue,配置router/index.js ,把他放到home  的children: 里; 2、SideMenu.vue,把其放入 LibraryIndex.vue中; 3、Books.vue,把其放入 LibraryIndex.vue中

六、增删改查

查询里涉及按关键字查询(图书检索),上传书籍信息里涉及图片上传

1、pojo层

新建两个 pojo,分别是Category 和 Book 

Book中:

@ManyToOne
@JoinColumn(name = "cid")
private Category category;

把 category 对象的 id 属性作为 cid 

2、dao层

新建一个 CategoryDAO ,一个 BookDAO 

BookDAO 里两个方法 :findAllByCategory、findAllByTitleLikeOrAuthorLike

JPA 提供默认方法

3、Service 层

CategoryService:   方法 : List<Category> list()、 Category get(int id)

BookService: 方法:List<Book> list()、void addOrUpdate(Book book)、void deleteById(int id)、List<Book> listByCategory(int cid)

四个功能:  分别是查出所有书籍、增加或更新书籍、通过 id 删除书籍和通过分类查出书籍

4、Controller层

GetMapping 与 PostMapping 

HTTP中的GETPOSTPUTDELETE就对应着对这个资源的 ,4个操作

GET一般用于获取/查询资源信息,而POST一般用于更新资源信息

换言之,查 get,增删改 post

PutMapping 与 DeleteMapping 这俩不咋用

get请求:a. 直接在浏览器地址栏输入某个地址;b. 点击链接;c. 表单默认的提交方式

Post请求: 设置表单method = “post”

LibraryController:
@RestController
public class LibraryController {
    @Autowired
    BookService bookService;

    @GetMapping("/api/books")
    public List<Book> list() throws Exception{
        return bookService.list();
    }

    @PostMapping("/api/books")
    public Book addOrUpdata(@RequestBody Book book) throws Exception{
        bookService.addOrUpdate(book);
        return book;
    }

    @PostMapping("/api/delete")
    public void delete(@RequestBody Book book) throws Exception{
        bookService.deleteById(book.getId());
    }

    @GetMapping("/api/categories/{cid}/books")
    public List<Book> listByCategory(@PathVariable("cid") int cid) throws Exception{
        if(cid != 0){
            return bookService.listBycategory(cid);
        }else {
            return list();
        }

    }

测试:查询所有书籍,访问 http://localhost:8443/api/books;测试分类,访问 http://localhost:8443/api/categories/1/books

综上所述:

pojo层、dao层、Service 层、Controller层

查:LibraryController list()方法,调用  bookService.list()方法, 其调用 bookDAO.findAll 方法,(JPA 默认提供的方法)

改:addOrUpdata() 方法,调用 bookService.addOrUpdate(book), 在调用 bookDAO.save(book) (JPA 默认提供的方法)

删:delete()方法,调用 bookService.deleteById(), 在调用 bookDAO.deleteById(id)    (JPA 默认提供的方法)

按类别查: listByCategory方法,  调用 bookService.listBycategory(cid), bookDAO.findAllByCategory(category),

bookdao中自定义的 public List<Book> findAllByCategory(Category category),也没写具体的实现逻辑。

七、增删改查功能的前端实现

发送请求调用后端编写好的接口,再根据返回的结果动态渲染页面。

1.EditForm.vue(新增)

该组件增加或者修改图书的弹出表单。 library 文件夹下

<el-dialog>  <el-form>    <el-form-item>    <el-button>

两个方法:clear ()、onSubmit ()

2.SearchBar.vue(新增)

该组件搜索的搜索框

<el-input>   <el-button>
方法:searchClick ()

3.Books.vue(修改)

图书管理页面的核心组件,添加搜索框,添加增加、删除按钮,完善分页功能,构造增、删、改、查对应的请求

<template>里:
<search-bar @onSearch="searchResult" ref="searchBar"></search-bar>
<el-tooltip  v-for="item in books.slice((currentPage-1)*pagesize,currentPage*pagesize)"> 

<edit-form @onSubmit="loadBooks()" ref="edit"></edit-form>

<el-pagination>

<script>里:

components:{EditForm, SearchBar},

4.LibraryIndex.vue(修改)

修改实现按分类查询

5.SideMenu.vue(修改)

实现了点击分类引发查询事件

 

1>查询功能:

  • 打开页面,默认查询出所有图书并显示(即页面的初始化)
  • 点击左侧分类栏,按照分类显示图书
  • 在搜索栏中输入作者或书名,可以模糊查询出相关书籍

1、页面初始化

在打开页面时就自动触发相应代码发送请求并渲染页面

Vue 的 钩子函数 —— mounted

mounted 即 “已挂载” ,所谓挂载,就是我们写的 Vue 代码被转换为 HTML 并替换相应的 DOM树的 这个过程;这个过程完事儿的时候,就会执行 mounted 里面的代码

books.vue: <script>里, loadBooks方法写在methods里 

    mounted: function () {
      this.loadBooks()
    },
loadBooks () {
        var _this = this
        this.$axios.get('/books').then(resp => {
          if (resp && resp.status === 200) {
            _this.books = resp.data
          }
        })
      }

利用 axios 发送了一个 get 请求,在接受到后端返回的成功代码后把 data 里的数据替换为后端返回的数据。利用 data 和 template 里相应元素的双向绑定,实现页面的动态渲染。

关于mounted的描述,el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。 main.js 里 new了一个vue对象,就是 vm.$el,id为app,在index.html是一个div,

2、分类查询

listByCategory方法写在LibraryIndex.vue中的methods里,    

点击左侧导航栏,向后端发送一个带有参数的 get 请求,然后同样是修改 data 里的数据以实现动态渲染

      listByCategory () {
        var _this = this
        var cid = this.$refs.sideMenu.cid
        var url = 'categories/' + cid + '/books'
        this.$axios.get(url).then(resp => {
          if (resp && resp.status === 200) {
            _this.$refs.booksArea.books = resp.data
          }
        })
      }

 组件之间的通信

在 LibraryIndex 组件的方法里,我们需要获取 SideMenu组件的 data cid

在LibraryIndex中,给SideMenu组件,ref="sideMenu"给 <SideMenu> 设置引用名。

<SideMenu @indexSelect="listByCategory" ref="sideMenu"></SideMenu>

这样,我们就可以通过   _this.refs.sideMenu  来引用侧面导航栏的实例,并获取它的数据 cid 了。

另外,前一句代码是  为 listByCategory() 方法设置了触发自定义事件 indexSelect。

在SideMenu.vue中,<script>里定义了一个方法:

methods: {
  handleSelect (key, keyPath) {
    this.cid = key
    this.$emit('indexSelect')
  }
}
emit,即触发,在子组件中使用 $emit 方法,即可触发在父组件中定义的事件。
handleSelect方法,在SideMenu.vue中,<template>  <el-menu>  里,定义了 @select="handleSelect",即 由事件select 执行 handleSelect方法

总结:

点击选择 侧边导航栏 的一个标签后,

触发SideMenu.vue中  <template> <el-menu> 里的 select 事件,执行 handleSelect方法;

然后handleSelect方法 触发 父组件 LibraryIndex.vue 中 <template> <SideMenu>里的  indexSelect 事件,执行  listByCategory 方法;

同时 传参 将 SideMenu.vue中  <el-menu-item> 标签的 index 属性,赋值给 data 中定义的 cid;

前端利用axios  发送get请求,后端执行查询代码,返回数据  _this.$refs.booksArea.books = resp.data , 

注意 get() 括号里的url 与 后端 @GetMapping() 里的url 保持一致。

3、搜索栏查询

关于Controller 方法里的 传参数 注释 理解有下:

(1)@RequestBody  前端发请求 传过来的 对象,例如  @RequestBody Book book

(2)@PathVariable    url路径里 带着的 信息,用这个 获得,例如  @PathVariable("cid") int cid

(3)@RequestParam  前端发请求 传过来的 参数 ,例如  @RequestParam("keywords") String keywords

bookservice里添加一个search方法,调用 bookDAO.findAllByTitleLikeOrAuthorLike(), librarycontroller里 search方法 调用 bookservice.search。

 

前端  核心的组件是 SearchBar,核心的方法写在 Books.vue (SearchBar 的父组件)里。

还是 父子组件调用、axios 请求 这一套。

 

SearchBar.vue里,

定义了  searchClick 方法, @keyup.enter.native="searchClick",@后的事件 执行了 该方法;

searchClick () {
  this.$emit('onSearch')
}

然后因为这句,searchClick方法 又触发了 父组件的 onSearch事件;

Books.vue里,<search-bar @onSearch="searchResult" ref="searchBar"></search-bar>,根据这句

onSearch事件 又 执行了 searchResult 方法。

 

<template>里都是 事件执行方法,<script>里有 方法触发父组件的事件。

2>增加、修改、删除 功能:

前端  发送完请求后,在接收到后端返回的成功代码后,做法是:重新执行查询以显示修改后的数据;实现:执行查询对应的 Ajax 请求,利用双向绑定更新显示。

1、增加和修改

EditForm.vue、父组件 Books.vue

增加功能:

EditForm.vue中:

加号图标 <i @click="dialogFormVisible = true> 

dialog 组件 <el-dialog> 弹出对话框,  visble.sync 属性 默认隐藏 ,点加号时 才会显示。

两个方法:

clear(),在关闭输入框时清空原来的内容

onSubmit(),提交数据,并触发父组件Books.vue中定义的 onSubmit 事件,而这个事件对应的方法则是 loadBooks(),即查询出所有的书籍。

修改功能:

Books.vue 中,给书籍封面上 添加一个事件 执行editBook方法,

          <div class="cover" @click="editBook(item)">
            <img :src="item.cover" alt="封面">
          </div>
editBook 这个方法即负责弹出修改表单并渲染数据:

2、删除

Books.vue中

安排一个图标元素的点击事件:

<i class="el-icon-delete" @click="deleteBook(item.id)"></i>

 

post 不能像 get 请求那样直接把参数写在 url 里,而需要以键值对的方式传递。

八、图片上传与项目的打包部署

1、图片上传

上传文件的逻辑:

前端向后端发送 post 请求,后端对接收到的数据进行处理(压缩、格式转换、重命名等),并保存到服务器中指定的位置,再把该位置对应的 URL 返回给前端即可。

 

前端部分:

写一个 ImgUpload.vue,他的父组件是EditForm.vue。

EditForm.vue 中更改:

import ImgUpload from './ImgUpload';   

<img-upload @onUpload="uploadImg" ref="imgUpload"></img-upload>  ;

添加一个method,uploadImg () { this.form.cover = this.$refs.imgUpload.url }

 

后端部分:

主要问题:

  • 如何接收前端传来的图片数据并保存
  • 如何避免重名(图片资源的名字很可能重复,如不修改可能出现问题)

现在想 前端上传一个图像,(不管该文件在本地哪里),可以在resources/img里存下来。

1)新建 utils 包,创建一个工具类 StringUtils 并编写生成指定长度随机字符串的方法

2)在 LibraryController 中添加 PostMapping,添加方法 public String coversUpload(MultipartFile file)

对文件的操作,对接收到的文件重命名,但保留原始的格式。

3)把url的前缀 跟我们设置的图片资源文件夹,即 D:/workspace/img 对应起来。

在 config\MyWebConfigurer 中添加代码

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/api/file/**").addResourceLocations("file:" + "d:/workspace/img/");
    }

2、部署项目到服务器

选择一: 把前端项目部署在 web 服务器中,把后端项目部署在应用服务器

选择二: 把前端项目打包,作为后端项目的静态文件,再把后端项目部署在应用服务器

前后端分离项目,采用选择一

使用 web 服务器的好处有如下几点:可以实现反向代理,提高网站的安全性;方便维护,一些小的修改不必同时协调前后端开发人员;对静态资源的加载速度更快。

1、下载 nginx,解压缩

2、打开前端项目,执行 cnpm run build, dist 文件夹下将出现 static 和 index.html,  把它们拷贝进 nginx\html 下

3、nginx\conf\nginx.conf,找到 server 的配置处,把 listen 80 改为 listen 8081; 添加一条 location 配置   try_files $uri $uri/ /index.html;

4、在前端 router\index.js 里添加一条路由 

 

这样 访问 http://localhost:8081/ 会跳转到 /index,运行 nginx 根目录下的 nginx.exe,启动web服务器

 

部署tomcat服务器,springboot自带

首先打开后端项目的 pom.xml,修改 <packaging> 标签里的 war 为 jar ;maven install,项目的 target 文件夹下就会出现我们的 jar 包

 java -jar wj-1.0.0.jar 即可运行

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值