Springboot 前后端分离项目中遇见的一个小问题
在springboot前后端分离项目中遇见前端代码不能刷新的问题
在学习springboot前后端分离项目时,参照教程1简单实现了一个前后端登录页面。
教程1其实是一个伪前后端分离项目,但是对我们理解前后端分离项目的开发有一定的意义。
虽然具体的开发过程和项目结果和教程中的类似,但是作为项目记录,我还是想将我所有的开发流程在这里重写一遍,并将遇见的问题一一列举出来,作为一个记录。
1. 创建数据库表结构
表结构如图,基本就是一个登陆/注册的基本信息收集
2. 创建Springboot项目
本项目的构建过程如下:本项目使用IDEA作为主要开发工具,构建过程如下
2.1 创建新项目
2.2 使用Spring Initializr 构建springboot项目
如果选择的是Spring构建的就是spring项目而非springboot项目
这里,SDK选择本地的java版本,Initializr Server URL默认就像,确保网速稳定即可。【也可以选择Custom但是我没有使用过,具体可以查查网上的使用教程】
2.3 选择版本
这里Group和Artifact就是项目名称了,后期创建项目文件夹会以Artifact的名字创建,里面不要包含特殊字符就行
Type类型默认就行,
Language默认,
Packaging是选择打包模式,一般有jar和war。默认就行
JavaVersion,选择本地java版本就行,我选择8,这里默认是IDEA自带的版本
其他的内容默认就行,因为这是一个测试项目而非正式项目,所以大部分内容都是默认即可
2.4 选择依赖内容
因为这是一个前后端分离项目,所以我没有选择模板引擎的内容,如果选择了模板引擎一般使用的是Thymeleaf
这里 数据库选择的是Mysql,数据库操作框架选择是Mybatis.
因为是一个web应用,因此选择的了Springweb。
一般我们可能还需要选择Spring Devtools以用来进行热启动选项。
选择了上述依赖后,点击下一步
2.5 选择项目位置,创建项目
3 项目结构
项目结构如图所示
因为我是用的是Mybatis框架操作的数据库内容,因此这里需要一个针对Mybatis的xml配置文件用来操作数据。
【特别说明:】不同于eclipse,在IDEA中,我们需要将xml文件存放在resources文件夹中,而不能存在java文件中,不然无论你在application.properties如何配置mybatis的xml文件搜索路径都无法将xml文件编译到target目录内。这是因为IDEA默认希望所有的资源文件都在Resources目录下,java文件内只用写代码即可。也算是一个小坑吧。
4 具体代码
4.1 POJO包
4.1.1 User类
构建User类,完成对数据库的映射,以及完成对前端数据的接受工作
User类的内容如下:
package com.example.springbootdemoforweb2.Pojo;
/**
* 一个pojo类
*/
public class User {
private int id;
private String name;
private String passwd;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
4.1.2 构建后端返回给前端的数据结构类Result
Result类用于构建一个数据结构,这个结构包含了后端返回给前端的数据,将其组织起来,然后依据Controller层的@ResponseBody注解,将处理结果包装成json对象,返回给前端页面.完成对前后端数据的交互.
package com.example.springbootdemoforweb2.Pojo;
/**
* 定义后端向前端发送的数据结构
*/
public class Result<T> { // 这里T代表可以返回任意数据类型
private String msg;
private boolean success;
private T detail;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public T getDetail() {
return detail;
}
public void setDetail(T detail) {
this.detail = detail;
}
}
4.2 Mapper层
4.2.1 构建UserMapper 接口,定义数据库操作方法
package com.example.springbootdemoforweb2.Mapper;
import com.example.springbootdemoforweb2.Pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 构造UserMapper 操控数据库
*/
@Mapper
@Repository
public interface UserMapper {
public List<User> findAll();
public User findByName(User user);
public int delete(User user);
public void put(User user);
public Integer login(User user); // 这里使用Interger而不是Int是因为返回结果可能为空
public void regis(User user);
}
这里是一个接口 不需要实例化,Springboot会自动实例化这个接口并完成相关功能。因此我们只需要定义一个接口即可。
这里需要注意的是,我们不仅在UserMapper上使用了@Mapper注解,也使用了@Repository注解,之所以使用@Repository注解是因为在service层自动注入UserMapper对象时可能会报错,具体原因目前还没有弄清楚,有待观察。
4.3 Service层
4.3.1 在构建好Mapper层后,构建Service层完成相关具体的服务工作
package com.example.springbootdemoforweb2.Service;
import com.example.springbootdemoforweb2.Mapper.UserMapper;
import com.example.springbootdemoforweb2.Pojo.Result;
import com.example.springbootdemoforweb2.Pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 服务提供方
*/
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public Result login(User user){
Result result = new Result();
result.setSuccess(false);
result.setDetail(null);
try {
Integer id = userMapper.login(user);
if (id!=null){
// 代表查找成功
result.setSuccess(true);
result.setMsg("登录成功");
result.setDetail(userMapper.findByName(user));
}else{
result.setMsg("登录失败,该用户或者密码错误");
}
}catch (Exception e){
result.setMsg(e.getMessage());
e.printStackTrace();
}
return result;
}
public Result registry(User user){
Result result = new Result();
result.setSuccess(false);
result.setDetail(null);
try {
User exituser = userMapper.findByName(user);
if (exituser!=null){
//当该用户名已经存在,返回注册失败
result.setMsg("该用户名已经存在");
}else {
userMapper.regis(user);
result.setSuccess(true);
result.setMsg("注册成功");
result.setDetail(userMapper.findByName(user));
}
}catch (Exception e){
result.setMsg(e.getMessage());
e.printStackTrace();
}
return result;
}
}
4.4 Mybatis配置文件
4.4.1 构建UserMapper.xml配置文件
<?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.example.springbootdemoforweb2.Mapper.UserMapper">
<!-- 查找所有内容-->
<select id="findAll" resultType="User">
select * from user_info
</select>
<select id="findByName" parameterType="User" resultType="User">
select * from user_info where name = #{name}
</select>
<select id="login" parameterType="User" resultType="int">
select id from user_info where name=#{name} and passwd=#{passwd}
</select>
<insert id="regis" parameterType="User" >
insert into user_info (name,passwd) values (#{name},#{passwd})
</insert>
<delete id="delete" parameterType="User">
delete from user_info where name = #{name} and passwd = #{passwd}
</delete>
<update id="update" parameterType="User">
update user_info set passwd = #{passwd} where name=#{name}
</update>
</mapper>
这里,我们构建一个UserMapper.xml的配置文件,该文件是Mybatis的核心配置文件。其中namespace需要只想UserMapper这个借口,需要使用全局限定名称。然后再标签内写下所有操作数据库的方法。其中id就是UserMapper的方法名
4.5 Controller层
4.5.1 构建UserController完成功能映射
package com.example.springbootdemoforweb2.Controller;
import com.example.springbootdemoforweb2.Pojo.Result;
import com.example.springbootdemoforweb2.Pojo.User;
import com.example.springbootdemoforweb2.Service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/login")
@ResponseBody
public Result login(User user){
System.out.println("用户姓名:"+user.getName()+"\t用户密码:"+user.getPasswd());
return userService.login(user);
}
@RequestMapping("/registry")
@ResponseBody
public Result regis(User user){
return userService.registry(user);
}
}
功能映射配置很简单,这里使用@ResponseBody将函数的方法转换为json格式
如果使用了Thymeleaf,并将@ResponseBody删除,在propertis配置文件中指定了默认网页地址,就可以通过返回的名称直接定位到指定的网页中。不过这个是前后端不分离的做法。
在前后端分离的开发过程中,前后端一般通过json传递数据内容。因此这里可以通过配置@ResponseBody将返回的Result类型构造为Json格式
4.6 application.properties配置文件
# mysql配置
spring.datasource.url=jdbc:mysql://localhost:3306/zyc_web_test?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
# 端口配置
server.port=9090
# 静态资源配置
spring.resources.static-locations=classpath:/static
# 配置mybatis环境
mybatis.type-aliases-package=com.example.springbootdemoforweb2.Pojo
mybatis.mapper-locations=classpath:MapperXML/*.xml
该配置文件是springboot的核心配置文件,通过该配置文件可以设定springboot项目的各种信息。
这里我们需要注意到的是mybatis环境配置信息,其中mapper-locations配置了xml问津的路径,type-aliases-package配置了xml文件类resultType的简写路径。
其他的像Mysql配置,端口配置,静态资源配置就很容易理解了
4.7 静态文件
4.7.1 登录页面 login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/login" method="post">
<p>用户名:<input name="name" type="text"></p>
<p>密码:<input name="passwd" type="password"></p>
<input type="submit" value="登录">
</form>
</body>
</html>
这里是登录页面,因为这是一个简单地前后端分离登录页面,因此前端项目没有单独使用一个服务器部署
4.7.2 注册页面registry.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
</head>
<body>
<form action="/registry" method="post">
<p>用户名:<input name="name" type="text"></p>
<p>密码:<input name="passwd" type="password"></p>
<input type="submit" value="注册">
</form>
</body>
</html>
这里是注册页面
5 遇见的问题
其实,一开始登录页面以及注册页面中表格代码不是如上所示的内容,其初始内容如下:
<form action="/registry" method="post">
<p>用户名:<input name="username" type="text"></p>
<p>密码:<input name="passwd" type="password"></p>
<input type="submit" value="注册">
</form>
这里用户名的name属性是username,而不是完成版中的name。需要注意的是前端通过POST方法向后端提交数据时,后端可以通过getparameter方法获取指定name属性的值。而一开始,name属性的值是username
而非User对象中的name
,导致后端无法正确接收用户姓名。
当将前端的name属性修改为name
后发现还是无法正确接收用户姓名属性。在target编译文件中可以发现login.html和registry.html中相关属性已经修改成功。最后在chrome中发现,原来是chrome的缓存机制导致修改后的网页并没有立刻呈现出修改后的结果,其name属性仍然是username
而非name
。
其解决方法如下:
-
点击F12打开开发者模式
-
点击右上角的三个点
-
点击设置
-
点击
-
点击如上两个选项,让chrome缓存失效,保证每一次的页面都是最新的页面即可。
上一篇: AQS 共享锁模式