【基于SprintBoot+Mybatis+Mysql】电脑商城项目之上传头像和新增收货地址

🧸安清h:************个人主页****************

🎥个人专栏:【Spring篇】** 【计算机网络】 【Mybatis篇】**

**🚦作者简介:**一个有趣爱睡觉的intp,期待和更多人分享自己所学知识的真诚大学生。


目录

[🚀1.上传头像 -持久层](#🚀1.上传头像 -持久层)

✨1.1规划SQL语句

✨1.2设计接口和抽象方法

[✨1.3 接口的映射](#✨1.3 接口的映射)

✨1.4单元测试

[🚀2.上传头像 -业务层](#🚀2.上传头像 -业务层)

✨2.1规划异常

✨2.2设计接口和抽象方法

✨2.3抽象方法实现

[🚀3.上传头像 -控制层](#🚀3.上传头像 -控制层)

✨3.1规划异常

✨3.2处理异常

✨3.3设计请求

✨3.4实现请求

[🚀4.上传头像 -前端页面](#🚀4.上传头像 -前端页面)

🚀5.解决BUG

✨5.1更改默认的大小限制

✨5.2显示头像

✨5.3登录后显示头像

🎃1.新增收货地址-数据表创建

🎃2.新增收货地址-创建实体类

🎃3.新增收货地址-持久层

✨1.1各功能的开发顺序

✨1.2规划需要执行的SQL语句

✨1.3接口与抽象方法

✨1.4配置SQL映射

🎃4.新增收货地址-业务层

✨4.1规划异常

✨4.2接口与抽象方法

✨4.3实现抽象方法

🎃5.新增收货地址-控制层

✨5.1处理异常

✨5.2设计请求

✨5.3处理请求

🎃6.新增收货地址-前端页面


🚀1.上传头像 -持久层

✨1.1规划SQL语句

将对应的文件保存在操作系统上,然后再把这个文件路径给记录下来,因为记录路径是非常方便和便捷的,将来如果要打开这个文件,可以依据这个路径去找到这个文件。在数据库中需要保存这个文件的路径即可。将所有的静态资源(图片,文件,其他)放到某台电脑上,再把这台电脑作为一台单独的服务器使用。

对应的是一个更新用户avatar字段的sql语句。

sql 复制代码
update t_user set avatar=?,modified_user=?,modified_time=? 
where uid=?

✨1.2设计接口和抽象方法

UserMapper接口中来定义个抽象方法用于修改用户的头像。

javascript 复制代码
     /**
      * @Param("SQL映射文件中#{}占位符的变量名"):解决的问题,
      * 当SQL语句的占位符和映射接口方法参数名不一致时,需要将某个参数强行注入到某个占位符变量上时,
      * 可以使用@Param这个注解来标注映射关系
      *也就是说@Param("avatar")就相当于mapper.xml中的#{avatar}
      *
      * 根据用户的uid修改用户的头像
      * @param uid
      * @param avatar
      * @param modifiedUser
      * @param modifiedTime
      * @return 返回值为收影响的行数
      * */
     Integer updateAvatarByUid(
                       @Param("uid") Integer uid,
                       @Param("avatar") String avatar,
                       @Param("modifiedUser") String modifiedUser,
                       @Param("modifiedTime") Date modifiedTime);

✨1.3 接口的映射

UserMapper.xml文件中编写映射的SQL语句。

XML 复制代码
    <update id="updateAvatarByUid">
        update t_user set avatar=#{avatar},
                          modified_user=#{modifiedUser},
                          modified_time=#{modifiedTime}
        where uid=#{uid}
    </update>

✨1.4单元测试

在test->mapper包下的UserMapperTests类中编写测试方法updateAvatarByUid,代码如下:

java 复制代码
    @Test
    public void updateAvatarByUid(){
        userMapper.updateAvatarByUid(7,"/upload/avatar.png","管理员",new Date());
    }

🚀2.上传头像 -业务层

✨2.1规划异常

1.用户数据不存在,找不到对应的用户数据。

2.更新的时候,有未知异常的产生。

前面已开发完成,无需重新开发。

✨2.2设计接口和抽象方法

注释的快捷方法:/**+enter

java 复制代码
    /**
     * 修改用户的头像
     * @param uid 用户id
     * @param avatar 用户的头像
     * @param username 用户的名称
     */
    void changeAvatar(Integer uid,
                      String avatar,
                      String username);

✨2.3抽象方法实现

编写业务层的更新头像的方法。

java 复制代码
    @Override
    public void changeAvatar(Integer uid, String avatar, String username) {
        User result = userMapper.findByUid(uid);
        if(result == null || result.getIsDelete() == 1){
            throw new UserNotFoundException("用户数据不存在");
        }
        Integer rows = userMapper.updateAvatarByUid(uid,avatar, username, new Date());
        if(rows != 1){
            throw new UpdateException("更新时数据产生未知的异常");
        }
    }

测试业务层方法的执行。

java 复制代码
    @Test
    public void changeAvatar(){
        userService.changeAvatar(7,"/upload/test.png","haha");
    }

🚀3.上传头像 -控制层

✨3.1规划异常

文件异常的父类:

FileUploadException 泛指文件上传的异常。(父类)继承RuntimeException

父类是:FileUploadException

FileEmptyException 文件为空的异常。

FileSizeException 文件大小超出限制的异常。

FileStateException 文件状态产生异常,即文件在打开状态时无法上传。

FileTypeException 文件类型异常。

FileUploadIOException 文件读写的异常。

五个构造方法显式声明出来,再去继承相关的父类。

在controller包下新建包ex,在ex包里新建以上六个类。其中父类FileUploadException继承RuntimeException,其余五个类继承父类FileUploadException,在此不再做过多代码描述。

✨3.2处理异常

在基类BaseController类中进行编写和统一处理。

java 复制代码
else if (e instanceof FileEmptyException) {
            result.setState(6000);
        } else if (e instanceof FileSizeException) {
            result.setState(6001);
        } else if (e instanceof FileTypeException) {
            result.setState(6002);
        } else if (e instanceof FileStateException) {
            result.setState(6003);
        } else if (e instanceof FileUploadIOException) {
            result.setState(6004);
        }

在异常统一处理方法的参数列表上增加新的异常处理作为它的参数。

✨3.3设计请求

请求路径:/users/change_avatar

请求方式:POST(get请求提交数据2KB,不够清晰)

请求数据:HttpSession session,MutipartFile file

响应结果:JsonResult<String>(记录图片的路径,图片路径是一个串,即用String,如果不记录下来,再切换到其他页面就展示不出来了)

✨3.4实现请求

MultipartFile接口是SpringMVC提供一个接口,这个接口为我们包装了获取文件类型的数据(任何类型的file都可以接受),SpringBoot它又整合了SpringMVC,只需要在处理请求的方法参数列表上声明一个参数类型为MultipartFile,然后SpringBoot会自动将传递给服务的文件数据赋值给这个参数。

@RequestParam:表示请求中的参数,将请求中的参数注入请求处理方法的某个参数上,如果名称不一致则可以使用@RequestParam注解进行标记和映射。

java 复制代码
/**
     *
     * @param session
     * @param file
     * @return
     */
    @RequestMapping("change_avatar")
    public JsonResult<String> changeAvatar(HttpSession session,
                                           @RequestParam("file") MultipartFile file) {
//        判断文件是否为空
        if(file.isEmpty()){
            throw new FileEmptyException("文件为空");
        }
//        判断文件大小是否超出限制
        if(file.getSize() > AVATAR_MAX_SIZE){
            throw new FileSizeException("文件超出限制");
        }
//        判断文件的类型是否为我们规定的和后缀类型
        String contentType = file.getContentType();
        if(!AVATAR_TYPE.contains(contentType)){
            throw new FileTypeException("文件类型不支持");
        }
//       上传的文件.../upload/文件.png
        String parent = session.getServletContext().getRealPath("upload");
//      File对象指向这个路径,File是否存在
      File dir = new File(parent);
      if(!dir.exists()){
          dir.mkdir();  //创建当前的目录
      }
//      获取到这个文件名称,UUID工具来将生成一个新的字符串作为文件名
//        返回的只是文件名,不包含目录结构.例如:avatar01.png。
//        在后续中,前面的avatar01即名称部分可以根据uid来生成一个随机的大小写字符串保存下来,
//        后缀需要保存下来
        String originalFilename =file.getOriginalFilename();
        System.out.println("OriginalFilename"+originalFilename);
        int index = originalFilename.lastIndexOf(".");  //查找字符串中最后一次出现的点号(.)的位置。
        String suffix = originalFilename.substring(index);  //从字符串的指定索引位置开始截取字符串,直到字符串的末尾。
        String filename = UUID.randomUUID().toString().toUpperCase() + suffix;
        File dest = new File(dir,filename);  //在dir下创建一个叫filename的文件,此时它是一个空文件
//        将参数file中的数据写入到空文件当中
        try{
            file.transferTo(dest);  //将file中的数据写入到dest文件中
        }catch (FileStateException e){
            throw new FileStateException("文件状态异常");
        }catch (IOException e){
            throw new FileUploadIOException("文件读写异常");
        }
        Integer uid = getuidFromSession(session);
        String username = getUsernameFromSession(session);
//        返回头像路径/upload/test.png,相对路径
        String avatar = "/upload"+filename;
        userService.changeAvatar(uid,avatar,username);
//        返回用户头像的路径给前端页面,将来用于头像的展示使用
        return new JsonResult<>(OK,avatar);
    }

这里没有办法测试,数据类型模拟不出来,file每一种类型的文件编码都不一样。

🚀4.上传头像 -前端页面

在upload页面中编写上传头像的代码。.ajax()请求它可以把一个数据解析成一个大串,来拼接在参数名的后边,但file可以直接把一个文件作为一个整体提交。表单中action可以整体提交。

说明:如果直接使用表单进行文件的上传,需要给表单显示的添加一个属性:

enctype="multipart/form-data";声明出来,不会将目标文件的数据结构做修改再上传,不同字符串。

🚀5.解决BUG

✨5.1更改默认的大小限制

SpringMVC默认为1MB文件可以进行上传,手动的去修改SpringMVC默认上传文件爱的大小。

**方式一:**直接可以在配置文件中进行配置:

java 复制代码
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB

✨5.2显示头像

页面中通过ajax的请求来提交文件,提交完成后返回了json串,解析出data中的数据,设置到img头像标签的src属性上就可以了。

1.在这里把submit改成button,因为submit不能够添加点击事件。

javascript 复制代码
<input id="btn-change-avatar" type="button" class="btn btn-primary" value="上传" />

2.这里原来的serialize提交的是一个串的类型,不能够解析文件,所以要换成类似一下方式:

  • serialize():可以将表单数据自动拼接成key=value的结构进行提交给服务器,一般提交是普通控件类型中的数据(text/password/radio/checkbox)。
  • FromData类:将表单中的数据保持原有的结构进行提交。new FromData($("#form")[0]);将form表单中某一个元素的整体值作为FromData()创建对象的数据,所以这个对象的格式就放在了这个对象当中,这样格式就不会被看做其他的对象被更改或调整。
  • ajax默认处理数据时按照字符串的形式进行处理,以及默认会采用字符串的形式进行提交数据。关闭这两个默认的功能。
javascript 复制代码
new FromData($("#form")[0])

✨5.3登录后显示头像

可以更新头像成功后,将服务器返回的头像路径保存在客户端的cookie对象中,然后每次检测到用户打开上传头像页面,在这个页面中通过ready()方法来自动监测去读取cookie中的头像并设到src上。

1.设置cookie中的值:

导入cookie.js的文件:

javascript 复制代码
<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
		<script src="../js/autoLogin.js" type="text/javascript"></script>

调用cookie的方法:

javascript 复制代码
$.cookie(key,value,time);  //单位:天(存活时间)

2.在upload.html页面先引入cookie.js文件

javascript 复制代码
<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
		<script src="../js/autoLogin.js" type="text/javascript"></script>

3.在upload.html页面通过ready()自动读取cookie中的数据。

javascript 复制代码
$(document).ready(function (){
				let avatar = $.cookie("avatar");
				// 将cookie中的值获取出来设置到头像src属性上
				$("#img-avatar").attr("src",avatar);
			});

5.4显示最新头像

在更改完头像后,将最新的头像地址,再次保存到cookie,同名保存会覆盖原有cookie中的值。

javascript 复制代码
$.cookie("avatar",json.data,{expires: 7});

🎃1.新增收货地址-数据表创建

sql 复制代码
CREATE TABLE t_address (
	aid INT AUTO_INCREMENT COMMENT '收货地址id',
	uid INT COMMENT '归属的用户id',
	`name` VARCHAR(20) COMMENT '收货人姓名',
	province_name VARCHAR(15) COMMENT '省-名称',
	province_code CHAR(6) COMMENT '省-行政代号',
	city_name VARCHAR(15) COMMENT '市-名称',
	city_code CHAR(6) COMMENT '市-行政代号',
	area_name VARCHAR(15) COMMENT '区-名称',
	area_code CHAR(6) COMMENT '区-行政代号',
	zip CHAR(6) COMMENT '邮政编码',
	address VARCHAR(50) COMMENT '详细地址',
	phone VARCHAR(20) COMMENT '手机',
	tel VARCHAR(20) COMMENT '固话',
	tag VARCHAR(6) COMMENT '标签',
	is_default INT COMMENT '是否默认:0-不默认,1-默认',
	created_user VARCHAR(20) COMMENT '创建人',
	created_time DATETIME COMMENT '创建时间',
	modified_user VARCHAR(20) COMMENT '修改人',
	modified_time DATETIME COMMENT '修改时间',
	PRIMARY KEY (aid)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

🎃2.新增收货地址-创建实体类

创建一个类Address类,在类中定义表的相关字段,采用驼峰命名方式,最后再去继承BaseEntity类。

java 复制代码
//收货地址数据的实体类
public class Address extends BaseEntity {
    private Integer aid;
    private Integer uid;
    private String name;
    private String provinceName;
    private String provinceCode;
    private String cityName;
    private String cityCode;
    private String areaName;
    private String areaCode;
    private String zip;
    private String address;
    private String phone;
    private String tel;
    private String tag;
    private Integer isDefault;
}

🎃3.新增收货地址-持久层

✨1.1各功能的开发顺序

当前收货地址功能模块:列表的展示、修改、删除、设置默认、新增收货地址。开发顺序:新增收货地址->列表展示->设置默认收货地址->删除收货地址->修改收货地址。

✨1.2规划需要执行的SQL语句

1.对应的插入语句:

sql 复制代码
insert into t_address(除aid外的字段列表) values(字段值列表)
  1. 一个用户的收货地址最多只能有20条数据对应。在插入用户数据之前先做查询操作。收货地址逻辑控制方面的一个异常。
sql 复制代码
select count(*) from t_address where uid=?

✨1.3接口与抽象方法

1.创建一个新的接口Address,在这个接口中来定义上面两个SQL语句抽象方法定义。

java 复制代码
/**
     * 插入用户的收货地址
     * @param address 收货地址数据
     * @return 受影响的行数
     */
    Integer insert(Address address);

    /**
     * 根据用户的id统计收货地址的数量
     * @param uid 用户的id
     * @return 当前用户的收货地址总数
     */
    Integer count(Integer uid);

✨1.4配置SQL映射

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.cy.store.mapper.AddressMapper">
    <resultMap id="AddressEntityMap" type="com.cy.store.entity.Address">
        <id column="aid" property="aid"/>
        <result column="province_code" property="provinceCode"/>
        <result column="province_name" property="provinceName"/>
        <result column="city_code" property="cityCode"/>
        <result column="city_name" property="cityName"/>
        <result column="area_code" property="areaCode"/>
        <result column="area_name" property="areaName"/>
        <result column="is_delete" property="isDelete"></result>
        <result column="created_user" property="createdUser"></result>
        <result column="created_time" property="createdTime"></result>
        <result column="modified_user" property="modifiedUser"></result>
        <result column="modified_time" property="modifiedTime"></result>
    </resultMap>

    <insert id="insert" useGeneratedKeys="true" keyProperty="aid">
        insert into t_address (
            uid, name, province_name, province_code, city_name, city_code, area_name, area_code, zip,
            address, phone, tel,tag, is_default, created_user, created_time, modified_user, modified_time
        ) values (
                     #{uid}, #{name}, #{provinceName}, #{provinceCode}, #{cityName}, #{cityCode}, #{areaName},
                     #{areaCode}, #{zip}, #{address}, #{phone}, #{tel}, #{tag}, #{isDefault}, #{createdUser},
                     #{createdTime}, #{modifiedUser}, #{modifiedTime}
                 )
    </insert>

    <select id="countByUid" resultType="java.lang.Integer">
        select count(*) from t_address where uid=#{uid}
    </select>
</mapper>

2.在test下的mapper文件夹下创建一个AddressMapperTests的测试类。

java 复制代码
@Test
    public void insert(){
        Address address = new Address();
        address.setUid(6);
        address.setPhone("15374583927");
        address.setName("备备");
        addressMapper.insert(address);
    }

    @Test
    public void countByUid(){
        Integer count = addressMapper.countByUid(6);
        System.out.println(count);
    }

🎃4.新增收货地址-业务层

✨4.1规划异常

1.如果用户是第一次插入收货地址,规则:当用户插入的地址是第一条时,需要将当前地址作为默认收货地址,如果查询到统计总数为0,则将当前地址的is_default设置为1。查询统计的结果为0不代表异常。

查询到的结果大于20,这时候需要抛出业务控制的异常AddressCountLimitException异常。自行创建这个异常。

java 复制代码
public class AddressCountLimitException extends ServiceException{
    public AddressCountLimitException() {
        super();
    }

    public AddressCountLimitException(String message) {
        super(message);
    }

    public AddressCountLimitException(String message, Throwable cause) {
        super(message, cause);
    }

    public AddressCountLimitException(Throwable cause) {
        super(cause);
    }

    protected AddressCountLimitException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

2.插入数据时产生异常。

✨4.2接口与抽象方法

1.创建一个IAddressService接口,在里面定义业务的抽象方法。

java 复制代码
    void addNewAddress(Integer uid,String username,Address address);

2.创建一个AddressServiceImpl实现类,去实现接口中的抽象方法。

在配置文件中定义数据

java 复制代码
#Spring读取配置文件中数据:@Value(${user.address.max-count})
user.address.max-count=20

✨4.3实现抽象方法

在实现类中实现业务控制 。

java 复制代码
//新增收货地址的实现类
@Service
public class AddressServiceImpl implements IAddressService {
    @Autowired
    private AddressMapper addressMapper;

    @Value("${user.address.max-count}")
    private Integer count;
    @Override
    public void addNewAddress(Integer uid, String username, Address address) {
//        调用收货地址统计的方法
        Integer count = addressMapper.countByUid(uid);
        if(count >= 20){
            throw new AddressCountLimitException("用户收货地址超出上限");
        }

        //    uid、isDelete
        address.setUid(uid);
        Integer isDelete = count == 0?1:0;  //1表示默认,0表示不默认
        address.setIsDefault(isDelete);
//    补全4项日志
        address.setCreatedUser(username);
        address.setCreatedTime(new Date());
        address.setModifiedUser(username);
        address.setModifiedTime(new Date());
        
//        插入收货地址的方法
        Integer rows = addressMapper.insert(address);
        if(rows != 1){
            throw new InsertException("插入用户地址时产生未知异常");
        }
    }


}

3.测试业务层功能是否正常。AddressServiceTests测试来测试业务功能。

java 复制代码
    @Test
    public void addNewAddress(){
        Address address = new Address();
        address.setPhone("11874583927");
        address.setName("备备");
        addressService.addNewAddress(6,"小明",address);
    }

🎃5.新增收货地址-控制层

✨5.1处理异常

业务层抛出了收货地址总数超标的异常,在BaseController中进行处理。

java 复制代码
else if(e instanceof AddressCountLimitException) {
            result.setState(4003);
            result.setMessage("用户收货地址超出上限的异常");
        }

✨5.2设计请求

请求路径:/addresses/add_new_address

请求方式:POST

请求数据:Address address,HttpSession session

响应结果:JsonResult<Void>

✨5.3处理请求

在控制层创建AddressController来处理用户收货地址的请求和响应。

java 复制代码
 @RequestMapping("add_new_address")
    public JsonResult<Void> addNewAddress(Address address, HttpSession session){
        Integer uid = getuidFromSession(session);
        String username = getUsernameFromSession(session);
        addressService.addNewAddress(uid,username,address);
        return new JsonResult<>(OK);
    }

先登录用户,然后再访问http://localhost:8080/address/add_new_address?name=tom&phone=1826478328 进行测试。

🎃6.新增收货地址-前端页面

javascript 复制代码
<script>
			$("#btn-add-new-address").click(function (){
				$.ajax({
					url:"/addresses/add_new_address",
					type:"POST",
					data:$("#form-add-new-address").serialize(),
					dataType:"JSON",
					success:function (json){
						if(json.state==200){
							alert("新增收货地址成功");
						}else{
							alert("新增收货地址失败");
						}
					},
					error:function (xhr){
						alert("新增收货地址时产生未知的异常"+xhr.message);
					}
				});
			});
		</script>
相关推荐
q_19132846955 小时前
基于SpringBoot2+Vue2的企业合作与活动管理平台
java·vue.js·经验分享·spring boot·笔记·mysql·计算机毕业设计
凌冰_5 小时前
JAVA与MySQL实现银行管理系统
java·开发语言·mysql
Han.miracle5 小时前
Spring WebMVC入门实战:从概念到连接建立全解析
java·spring boot·spring·springmvc
NineData5 小时前
NineData 数据库 DevOps 正式支持谷歌云,全面接入 GCP 数据源
运维·数据库·devops·ninedata·gcp·玖章算术·数据智能管理平台
TT哇5 小时前
Spring Boot 项目中关于文件上传与访问的配置方案
java·spring boot·后端
程序员阿周5 小时前
boost、websocketpp、curl 编译(Windows)
后端
踏浪无痕6 小时前
信不信?一天让你从Java工程师变成Go开发者
后端·go
浪里行舟6 小时前
使用亚马逊云科技 Elemental MediaConvert 实现 HLS 标准加密
后端
韩立学长6 小时前
Springboot考研自习室预约管理系统1wdeuxh6(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
残花月伴6 小时前
天机学堂-day5(互动问答)
java·spring boot·后端