系统概述与环境搭建 1 系统开发及运行环境 电脑商城系统开发所需的环境及相关软件进行介绍。
1.操作系统:Windows 10
2.Java开发包:JDK 8
3.项目管理工具:Maven 3.6.3
4.项目开发工具:IntelliJ IDEA 2020.3.2 x64
5.数据库:MariaDB-10.3.7-winx64
6.浏览器:Google Chrome
7.服务器架构:Spring Boot 2.4.7 + MyBatis 2.1.4 + AJAX
2 项目分析 1.在开发某个项目之前,应先分析这个项目中可能涉及哪些种类的数据。本项目中涉及的数据:用户、商品、商品类别、收藏、订单、购物车、收货地址。
2.关于数据,还应该要确定这些数据的开发顺序。设计开发顺序的原则是:先开发基础、简单或熟悉的数据。以上需要处理的数据的开发流程是:用户-收货地址-商品类别-商品-收藏-购物车-订单。
3.在开发每种数据的相关功能时,先分析该数据涉及哪些功能。在本项目中以用户数据为例,需要开发的功能有:登录、注册、修改密码、修改资料、上传头像。
4.然后,在确定这些功能的开发顺序。原则上,应先做基础功能,并遵循增查删改的顺序来开发。则用户相关功能的开发顺序应该是:注册-登录-修改密码-修改个人资料-上传头像。
5.在实际开发中,应先创建该项目的数据库,当每次处理一种新的数据时,应先创建该数据在数据库中的数据表,然后在项目中创建该数据表对应的实体类。
6.在开发某个具体的功能时,应遵循开发顺序:持久层-业务层-控制器-前端页面。
3 创建数据库 1.首先确保计算机上安装了MariaDB-10.3.7-winx64数据库,将来在数据库中创建与项目相关的表。
2.创建电脑商城项目对应的后台数据库系统store。
1 CREATE DATABASE store character SET utf8;
4 创建Spring Initializr项目 本质上Spring Initializr是一个Web应用程序,它提供了一个基本的项目结构,能够帮助开发者快速构建一个基础的Spring Boot项目。在创建Spring Initializr类型的项目时需在计算机连网的状态下进行创建。
1.首先确保计算机上安装了JDK、IDEA、MariaDB等开发需要使用的软件,并在IDEA中配置了Maven 3.6.3项目管理工具。
2.在IDEA欢迎界面,点击【New Project】按钮创建项目,左侧选择【Spring Initializr】选项进行Spring Boot项目快速构建。
3.将Group设置为com.cy,Artifact设置为store,其余选项使用默认值。单击【Next】进入Spring Boot场景依赖选择界面。
4.给项目添加Spring Web、MyBatis Framework、MySQL Driver的依赖。点击【Next】按钮完成项目创建。
5.首次创建完Spring Initializr项目时,解析项目依赖需消耗一定时间(Resolving dependencies of store…)。
5 配置并运行项目 5.1 运行项目 找到项目的入口类(被@SpringBootApplication注解修饰),然后运行启动类;启动过程如果控制台输出Spring图形则表示启动成功。
1 2 3 4 5 6 7 8 9 10 package com.cy.store;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class StoreApplication { public static void main (String[] args) { SpringApplication.run(StoreApplication.class, args); } }
5.2 配置项目 1.如果启动项目时提示:“配置数据源失败:’url’属性未指定,无法配置内嵌的数据源”。有如下的错误提示。
1 Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
2.解决以上操作提示的方法:在resources文件夹下的application.properties文件中添加数据源的配置。
1 2 3 spring.datasource.url =jdbc:mysql://localhost:3306/store?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai spring.datasource.username =root spring.datasource.password =123456
3.为了便于查询JSON数据,隐藏没有值的属性,减少流量的消耗,服务器不应该向客户端响应为NULL的属性。可以在属性或类之前添加@JsonInclude(value=Include.NON_NULL),也可以在application.properties中添加全局的配置。
1 2 spring.jackson.default-property-inclusion =NON_NULL
4.SpringBoot项目的默认访问名称是“/”,如果需要修改可以手动在配置文件中指定SpringBoot 2.x访问项目路径的项目名。不建议修改。
1 server.servlet.context-path =/store
5.重新启动项目,则不在提示配置数据源失败的问题。
用户注册 1、创建数据库表(store) 1.选中数据库:
2.创建t_user
表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 create table `t_user` ( `uid` int (11 ), `username` varchar (60 ), `password` char (96 ), `salt` char (108 ), `phone` varchar (60 ), `email` varchar (90 ), `gender` int (11 ), `avatar` varchar (150 ), `is_delete` int (11 ), `created_user` varchar (60 ), `created_time` datetime , `modified_user` varchar (60 ), `modified_time` datetime );
2、创建用户的实体类 1.通过表的结构提取出表的公共字段,放在一个实体类的基类中(BaseEntity)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package com.amh.entity;import java.io.Serializable;import java.util.Date;import java.util.Objects;public class BaseEntity implements Serializable { private String createdUser; private Date createdTime; private String modifiedUser; private Date modifiedTime; public String getCreatedUser () { return createdUser; } public void setCreatedUser (String createdUser) { this .createdUser = createdUser; } public Date getCreatedTime () { return createdTime; } public void setCreatedTime (Date createdTime) { this .createdTime = createdTime; } public String getModifiedUser () { return modifiedUser; } public void setModifiedUser (String modifiedUser) { this .modifiedUser = modifiedUser; } public Date getModifiedTime () { return modifiedTime; } public void setModifiedTime (Date modifiedTime) { this .modifiedTime = modifiedTime; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; BaseEntity that = (BaseEntity) o; return Objects.equals(createdUser, that.createdUser) && Objects.equals(createdTime, that.createdTime) && Objects.equals(modifiedUser, that.modifiedUser) && Objects.equals(modifiedTime, that.modifiedTime); } @Override public int hashCode () { return Objects.hash(createdUser, createdTime, modifiedUser, modifiedTime); } @Override public String toString () { return "BaseEntity{" + "createdUser='" + createdUser + '\'' + ", createdTime=" + createdTime + ", modifiedUser='" + modifiedUser + '\'' + ", modifiedTime=" + modifiedTime + '}' ; } }
2.创建用户的实体类(User),继承BaseEntity类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 package com.amh.entity;import java.io.Serializable;import java.util.Objects;public class User extends BaseEntity implements Serializable { private Integer uid; private String username; private String password; private String salt; private String phone; private String email; private Integer gender; private String avatar; private Integer isDelete; public Integer getUid () { return uid; } public void setUid (Integer uid) { this .uid = uid; } public String getUsername () { return username; } public void setUsername (String username) { this .username = username; } public String getPassword () { return password; } public void setPassword (String password) { this .password = password; } public String getSalt () { return salt; } public void setSalt (String salt) { this .salt = salt; } public String getPhone () { return phone; } public void setPhone (String phone) { this .phone = phone; } public String getEmail () { return email; } public void setEmail (String email) { this .email = email; } public Integer getGender () { return gender; } public void setGender (Integer gender) { this .gender = gender; } public String getAvatar () { return avatar; } public void setAvatar (String avatar) { this .avatar = avatar; } public Integer getIsDelete () { return isDelete; } public void setIsDelete (Integer isDelete) { this .isDelete = isDelete; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; if (!super .equals(o)) return false ; User user = (User) o; return Objects.equals(uid, user.uid) && Objects.equals(username, user.username) && Objects.equals(password, user.password) && Objects.equals(salt, user.salt) && Objects.equals(phone, user.phone) && Objects.equals(email, user.email) && Objects.equals(gender, user.gender) && Objects.equals(avatar, user.avatar) && Objects.equals(isDelete, user.isDelete); } @Override public int hashCode () { return Objects.hash(super .hashCode(), uid, username, password, salt, phone, email, gender, avatar, isDelete); } @Override public String toString () { return "User{" + "uid=" + uid + ", username='" + username + '\'' + ", password='" + password + '\'' + ", salt='" + salt + '\'' + ", phone='" + phone + '\'' + ", email='" + email + '\'' + ", gender=" + gender + ", avatar='" + avatar + '\'' + ", isDelete=" + isDelete + '}' ; } }
3、注册-持久层 通过Mybatis来操作数据库,在做,mybatis开发的流程
3.1 规划需要执行的SQL语句 1.用户的注册功能,相当于在做数据的插入操作
1 insert into t_user (username , password) values (值列表)
2.在用户注册时,首先查询数据库是否存在该用户名
1 select username from t_user where username = ?
3.2 设计接口的抽象方法 1.定义Mapper接口,在项目的目录结构下创建一个mapper包,在这个包下在根据不同的功能模块来创建mapper接口,创建一个UserMapper的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.amh.mapper;import com.amh.entity.User;import org.apache.ibatis.annotations.Mapper;public interface UserMapper { Integer insert (User user) ; User findByUsername (String username) ; }
2.在启动类配置mapper接口文件的位置
1 2 @MapperScan("com.amh.mapper")
3.3编写映射 1.定义xml映射文件,在对应的接口进行关联。所有的映射文件需要放置在resources目录下,(为了方便管理)在这个目录下创建一个mapper文件夹,然后在这个文件夹在存放Mapper的映射文件
2.创建接口对应的映射文件,遵循和接口的名称保持一致即可。创建一个UserMapper.xml文件。
1 2 3 4 5 6 <?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.amh.mapper.UserMapper" > </mapper >
2.配置接口中的方法对应上sql语句上,需要借助标签来完成,insert\update\delete\select,对应的是数据库的增删改查操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?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.amh.mapper.UserMapper" > <insert id ="insert" useGeneratedKeys ="true" keyProperty ="uid" > INSERT INTO stores.t_user( username, password, salt, phone, email, gender, avatar, is_delete, created_user, created_time, modified_user, modified_time ) VALUES ( #{username}, #{password}, #{salt}, #{phone}, #{email}, #{gender}, #{avatar}, #{isDelete}, #{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime} ) </insert > <resultMap id ="User" type ="com.amh.entity.User" > <id column ="uid" property ="uid" > </id > <result column ="isDelete" property ="isDelete" > </result > <result column ="createdUser" property ="createdUser" > </result > <result column ="createdTime" property ="createdTime" > </result > <result column ="modifiedUser" property ="modifiedUser" > </result > <result column ="modifiedTime" property ="modifiedTime" > </result > </resultMap > <select id ="findByUsername" resultMap ="User" > SELECT username from stores.t_user WHERE username = #{username} </select > </mapper >
3.将mapper文件的位置注册到项目的配置文件中。
1 2 mybatis: mapper-locations: classpath:mapper/*.xml
4.单元测试:每个独立的层编写完毕后需要编写单元测试的方法,来测试当前的功能。在test包结构下创建mapper包,在这个包下再创建持久层的功能测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.amh.mapper;import com.amh.entity.User;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest @RunWith(SpringRunner.class) public class UserMapperTests { @Autowired(required = false) private UserMapper userMapper; @Test public void insert () { User user=new User(); user.setUsername("amh" ); user.setPassword("123" ); Integer insert = userMapper.insert(user); System.out.println(insert); } @Test public void findByUsername () { System.out.println(userMapper.findByUsername("amh" )); } }
4、注册-业务层 4.1规划异常 1.RuntimeException异常,在用户操作所产生的异常我们可以作为这个异常的子类,然后再去定义具体的异常类型来继承这个异常。创建一个业务层异常的基类(ServiceException),让这个异常去继承RuntimeException异常。异常机制的建立
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.amh.service.ex;public class ServiceException extends RuntimeException { public ServiceException () { super (); } public ServiceException (String message) { super (message); } public ServiceException (String message, Throwable cause) { super (message, cause); } public ServiceException (Throwable cause) { super (cause); } protected ServiceException (String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super (message, cause, enableSuppression, writableStackTrace); } }
根据业务层不同的功能来详细定义具体的异常类型,统一去继承ServiceException异常类
2.用户在进行注册时候可能会产生用户名被占用的错误,抛出一个异常:UsernameDuplicatedException异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.amh.service.ex;public class UsernameDuplicatedException extends ServiceException { public UsernameDuplicatedException () { super (); } public UsernameDuplicatedException (String message) { super (message); } public UsernameDuplicatedException (String message, Throwable cause) { super (message, cause); } public UsernameDuplicatedException (Throwable cause) { super (cause); } protected UsernameDuplicatedException (String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super (message, cause, enableSuppression, writableStackTrace); } }
3.正在执行数据插入操作时,服务器,数据库宕机。处于正在执行插入的过程中所产生的异常 InsertException异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.amh.service.ex;public class InsertException extends ServiceException { public InsertException () { super (); } public InsertException (String message) { super (message); } public InsertException (String message, Throwable cause) { super (message, cause); } public InsertException (Throwable cause) { super (cause); } protected InsertException (String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super (message, cause, enableSuppression, writableStackTrace); } }
4.2 设计接口和抽象方法 在service包下创建一个IUserService接口
1 2 3 4 5 6 7 8 9 10 11 12 package com.amh.service;import com.amh.entity.User;public interface IUserService { void reg (User user) ; }
2.创建一个实现类UserServiceImpl类,需要实现这个接口,并且实现抽象方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 package com.amh.service.impl;import com.amh.entity.User;import com.amh.mapper.UserMapper;import com.amh.service.IUserService;import com.amh.service.ex.InsertException;import com.amh.service.ex.UsernameDuplicatedException;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.util.DigestUtils;import java.util.Date;import java.util.UUID;@Service public class UserServiceImpl implements IUserService { @Autowired private UserMapper userMapper; @Override public void reg (User user) { User result = userMapper.findByUsername(user.getUsername()); if (result != null ) { throw new UsernameDuplicatedException("用户名被占用" ); } String oldPassword = user.getPassword(); String salt = UUID.randomUUID().toString().toUpperCase(); user.setSalt(salt); String md5Password = gitMD5Password(oldPassword, salt); user.setPassword(md5Password); user.setIsDelete(0 ); user.setCreatedUser(user.getUsername()); user.setModifiedUser(user.getUsername()); Date date = new Date(); user.setCreatedTime(date); user.setModifiedTime(date); Integer rows = userMapper.insert(user); if (rows != 1 ) { throw new InsertException("在用户注册时,发生了未知的异常" ); } } private String gitMD5Password (String password, String salt) { for (int i = 0 ; i < 3 ; i++) { password = DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()).toUpperCase(); } return password; } }
3.在单元测试包下创建一个UserServiceTest类,在这个类中添加单元测试的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.amh.service;import com.amh.entity.User;import com.amh.mapper.UserMapper;import com.amh.service.ex.ServiceException;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest @RunWith(SpringRunner.class) public class UserServiceTest { @Autowired(required = false) private IUserService IUserService; @Test public void reg () { try { User user=new User(); user.setUsername("amh" ); user.setPassword("123" ); IUserService.reg(user); System.out.println("OK" ); } catch (ServiceException e) { System.out.println(e.getClass().getSimpleName()); System.out.println(e.getMessage()); } } }
5、注册-控制层 5.1 创建响应 状态码、状态信息的描述、数据、这部分功能封装一个类中,将这类作为方法的返回值,返回给前端浏览器。
1 2 3 4 5 6 7 8 9 10 11 public class JsonResult <E > implements Serializable { private Integer state; private String message; private E data; }
5.2 设计请求 依据当前的业务功能模块进行请求的设计。
1 2 3 4 请求路径:/users/reg 请求参数:User user 请求类型:POST 响应结果:JsonResult<void>
5.3 处理请求 1.创建一个控制层对应的类UserController类,依赖于业务层的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.amh.controlle;import com.amh.entity.User;import com.amh.service.IUserService;import com.amh.service.ex.InsertException;import com.amh.service.ex.UsernameDuplicatedException;import com.amh.util.JsonResult;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;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("users") public class UserController { @Autowired private IUserService userService; @RequestMapping("reg") public JsonResult<Void> reg (User user) { JsonResult<Void> result = new JsonResult<>(); try { userService.reg(user); result.setState(200 ); result.setMessage("用户注册成功" ); } catch (UsernameDuplicatedException e) { result.setState(4000 ); result.setMessage("用户名被占用" ); } catch (InsertException e) { result.setState(5000 ); result.setMessage("注册时产生未知的错误" ); } return result; } }
5.4 控制层优化设计 在控制器抽离一个父类,在这个父类中统一的去处理关于异常的相关操作,编写BaseController类,统一处理异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.amh.controlle;import com.amh.service.ex.InsertException;import com.amh.service.ex.ServiceException;import com.amh.service.ex.UsernameDuplicatedException;import com.amh.util.JsonResult;import org.springframework.web.bind.annotation.ExceptionHandler;public class BaseController { public static final int OK = 200 ; @ExceptionHandler(ServiceException.class) public JsonResult<Void> handleException (Throwable e) { JsonResult<Void> result = new JsonResult<>(); if (e instanceof UsernameDuplicatedException) { result.setState(4000 ); result.setMessage("用户名被占用" ); } else if (e instanceof InsertException) { result.setState(5000 ); result.setMessage("注册时产生未知的错误" ); } return result; } }
优化后的UserController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package com.amh.controlle;import com.amh.entity.User;import com.amh.service.IUserService;import com.amh.service.ex.InsertException;import com.amh.service.ex.UsernameDuplicatedException;import com.amh.util.JsonResult;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;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("users") public class UserController extends BaseController { @Autowired private IUserService userService; @RequestMapping("reg") public JsonResult<Void> reg (User user) { userService.reg(user); return new JsonResult<>(OK); } }
6、注册-前端页面 1.在register页面中编写发送请求的方法,点击事件来完成,选选中对应的按键($(“选择器”)),再去添加点击的事件,$.ajax()函数发送异步请求。
2.JQuery封装了一个函数,称之为 $.ajax()函数,通过对象调用ajax()函数,可以异步加载相关的请求。
3.ajax()使用方法,需要传递一个方法体作为方法的参数来使用,一对大括号称之为方法体,ajax接收多个参数,参数与参数之间要求使用“ , ”进行分割,每一组参数之间使用“ : ”进行分割,参数的组成部分一个是参数的名称(不能随意的定义),是参数的值,参数的值要求是用字符串来标识,语法结构:
1 2 3 4 5 6 7 8 9 10 11 12 $.ajax({ url : "" , type : "" , data : "" , dataType : "" , success : function ( ) { }, error : function ( ) { } })
4.ajax()函数参数的含义:
参数
功能描述
url
标识请求的地址(url地址),不能包含参数列表部分的内容。例如:url:”localhost:8080/users/reg”
type
请求类型(GET和POST悄悄地类型)。例如:type:”POST”
data
向指定的请求url地址 提交数据。例如:data:”username=amh&password=123456”
dataType
提交的数据的类型,数据类型一般指定为JSON类型。例如:dataType:”json”
success
当服务器正常响应客户端时,会自动调用success参数的方法,并且将服务器返回的数据以参数的形式传递给这个方法的参数上
error
当服务器未正常响应客户端时,会自动调用error参数的方法,并且将服务器返回的数据以参数的形式传递给这个方法的参数上
5.js代码可以独立声明在一个js的文件里或者声明在英国script标签中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <script type="text/javascript" > $("#btn-reg" ).click(function ( ) { $.ajax({ url :"/users/reg" , type :"POST" , data :$("#form-reg" ).serialize(), dataType :"JSON" , success :function (json ) { if (json.state==200 ){ alert("注册成功" ); }else { alert("注册失败" ); } }, error :function (xhr ) { alert("注册时产生未知的错!" +xhr.status) } }); }); </script>
6.当js代码无法正常被服务器解析执行,体现在点击页面中的按钮没有任何的响应,解决方案:
在项目的maven下clear清理项目,install重新部署
在项目的file选项下,清理缓存
重新的去构建项目:build选项下,rebuild选项
重启idea
重启电脑
用户登录 当用户输入用户名和密码将数据提交给后台数据库进行查询,如果存在对应的用户名和密码,则表示登录成功,登录成功之后跳转到系统的主页就是index.html页面,跳转在前端使用jQuery来完成。
1.登录—持久层 1.1 规划需要执行的sql语句 依据用户提交的用户名和密码做select查询。密码的比较在业务层执行。
1 select * from t_user where username = ?
说明:如果在分析过程中发现某个功能模块已经被开发完成,所有就可以省略当前的开发步骤,这个分析过程不能够省略。
1.2 接口设计和方法
不用重复开发,单元测试也是无需单独执行了。
2.登录—业务层 2.1 规划异常 1.用户名对应密码错误。密码匹配失败的异常:PasswordNotMatchException异常,运行时异常,业务异常。
2.用户名没有被找到,抛出异常:UsernameNotFoundException,运行时异常,业务异常。
3.异常的编写
业务层异常需要继承ServiceException异常类
在具体的异常类中定义构造方法(可以使用快捷键来生成,有5个构造方法)。
2.2 设计业务层接口和抽象方法 1.直接在IUseService接口中编写抽象方法,login(Spring username,String password)。将当前登录成功的用户数据以当前用户对象的形式进行返回,状态管理:可以将数据保存在Cookie或者session中,可以避免重复度很高的数据多次频繁操作数据进行获取(=用户名、用户id—存放在session中,用户的头像–Cookie中)。
1 2 3 4 5 6 7 User login (String username,String password) ;
2.需要在实现类中实现父类接口的抽象方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Override public User login (String username, String password) { User result = userMapper.findByUsername(username); if (result==null ){ throw new UserNotFoundException("用户数据不存在" ); } String oldPassword = result.getPassword(); String salt = result.getSalt(); String newMd5Password = gitMD5Password(password, salt); if (!newMd5Password.equals(oldPassword)){ throw new PasswordNotMatchException("用户密码错误" ); } if (result.getIsDelete()==1 ){ throw new UserNotFoundException("用户数据不存在" ); } User user=new User(); user.setUid(result.getUid()); user.setUsername(result.getUsername()); user.setAvatar(result.getAvatar()); return user; }
3.在测试类中测试业务层登录的方法是否可以执行通过。
1 2 3 4 5 6 @Test public void login () { User user=IUserService.login("amh" ,"123" ); System.out.println(user); }
3.登录—控制器 3.1 处理异常 业务层抛出的异常是什么,需要在统一异常处理类中统一捕获和处理,如果也曾抛出的异常类型已经在统一异常处理类中曾经处理过,则不需要重复添加。
1 2 3 4 5 6 7 else if (e instanceof UserNotFoundException) { result.setState(5001 ); result.setMessage("用户数据不存在" ); }else if (e instanceof PasswordNotMatchException) { result.setState(5002 ); result.setMessage("用户名的密码错误" ); }
3.2 设计请求 1 2 3 4 请求路径:/users/login 请求参数:Spring username,Spring password,HttpSession session 请求类型:POST 响应结果:JsonResult<User>
3.3 处理请求 在UserController类中编写请求处理的方法
1 2 3 4 5 @RequestMapping("login") public JsonResult<User> login (String username,String password) { User data = userService.login(username, password); return new JsonResult<User>(OK,data); }
4.登录—前端页面 1.在login.html页面中依据前面所设置的请求来发ajax请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <script type="text/javascript" > $("#btn-login" ).click(function ( ) { $.ajax({ url : "/users/login" , type : "POST" , data : $("#form-login" ).serialize(), dataType : "JSON" , success : function (JSON ) { if (JSON .state==200 ){ alert("登录成功" ) location.href="index.html" }else { alert("登录失败" ) } }, error : function (xhr ) { alert("登录时产生未知的异常" +xhr.message) } }) }) </script>
2.访问页面进行用户的登录操作。
用户会话session session对象主要存储在服务器端,可以用于保存服务器的临时数据的对象,所保存的数据可以在整个项目中都可以通过访问来获取,把session的数据看做一个共享的数据,首次登录的时候所获取的用户的数据,转移到session对象即可。session。getAttrbute(“key”),可以将获取session中的数据这种行为进行封装,封装在BaseControllerl类中。
1.封装session对象中数据的获取(封装父类中),数据的设置(当用户登录成功后进行数据的设置,设置到全局的session对象)。
2.在父类中封装两个数据:获取uid和获取username对应的两个方法,用户头像暂时不考虑,将来封装Cookie中来使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 protected final Integer getuidFromSession (HttpSession session) { return Integer.valueOf(session.getAttribute("uid" ).toString()); } protected final String getUsernameFromSession (HttpSession session) { return session.getAttribute("username" ).toString(); }
3.在登录的方法中将数据封装在session对象中,服务本身自动创建有session对象,已经是一个全局我的session对象,SpringBoot直接使用session对象,直接将HttpSession类型的对象作为请求处理方法的参数,会自动将全局的session对象注入到请求处理方法的session形参上
1 2 3 4 5 6 7 8 9 10 11 12 13 @RequestMapping("login") public JsonResult<User> login (String username,String password,HttpSession session) { User data = userService.login(username, password); session.setAttribute("uid" ,data.getUid()); session.setAttribute("username" ,data.getUsername()); System.out.println(getuidFromSession(session)); System.out.println(getUsernameFromSession(session)); return new JsonResult<User>(OK,data); }
拦截器 拦截器:首先将所有的请求统一拦截到拦截器中,可以在拦截器中定义过滤的规则,如果不满足系统的设置的过滤规则,统一的处理是:重新去打开login.html页面(重定向和转发),推荐使用重定向。
在SpringBoot项目中拦截器的定义和使用。SpringBoot是依靠SpringMVC来完成的。SpringMVC提供了一个HandlerInterceptor接口,用于表示定义一个拦截器,首先自定义个类,在让这个类实现这个接口。
1.首先自定义一个类,在这个类实现这个HandlerInterceptor接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.amh.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object obj = request.getSession().getAttribute("uid" ); if (obj==null ){ response.sendRedirect("/web/login.html" ); return false ; } return true ; } }
2.注册过滤器:添加白名单(哪些资源可以在未登录的情况下访问:login.html\register.html\login\reg\index.html\product.html)、
添加黑名单(在用户登录的状态才可以访问的页面资源)。
3.注册过滤器的技术:借助WebMvcConfigure接口,可以将用户定义的拦截器进行注册,才可以保证拦截器能够生效和使用。定义一个类,然后让这个类是实现WebMvcConfigure接口。配置信息,建议存放在项目的config包的结构下。
1 2 3 default void addInterceptors (InterceptorRegistry registry) {}
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.amh.config;import com.amh.interceptor.LoginInterceptor;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.ArrayList;import java.util.List;@Configuration public class LoginInterceptorConfigurer implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { HandlerInterceptor interceptor=new LoginInterceptor(); List<String> patterns=new ArrayList<>(); patterns.add("/bootstrap3/**" ); patterns.add("/css/**" ); patterns.add("/images/**" ); patterns.add("/web/register.html" ); patterns.add("/web/login.html" ); patterns.add("/web/index.html" ); patterns.add("/web/product.html" ); patterns.add("/users/reg" ); patterns.add("/users/login" ); patterns.add("/index.html" ); patterns.add("/" ); registry.addInterceptor(interceptor) .addPathPatterns("/**" ) .excludePathPatterns(patterns); } }
4.提示重定向次数过多,login.html页面无法打开,将浏览器的Cookie请求清除,在将浏览器初始化
源码解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public interface HandlerInterceptor { default boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true ; } default void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
修改密码 需要用户提交原始密码和新密码,再根据当前登录的用户进行信息的修改操作。
1.修改密码-持久层 1.1 规划需要执行的sql语句 根据用户的uid修改用户password值
1 update t_user set password= ?,modified_user= ?.modified_time= ? where uid= ?
根据uid查询用户的数据。在修改密码之前。首先要保证当前这用户的数据存在,检测是否被标记为已删除,检测输入的原始密码是否正确。
1 select * from t_user where uid= ?
1.2 设计接口和抽象方法 UserMapper接口,将以上两个方法抽象定义出来,将来映射到sql语句上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Integer updatePasswordByUid (Integer uid, Spring password, String modifiedUser, Date modifiedTime) ; User findByUid (Integer uid) ;
1.3 SQL的映射 配置到映射文件UserMapper.xml中
1 2 3 4 5 6 7 8 9 10 11 <update id ="updatePasswordByUid" > update stores.t_user set password=#{password}, modified_user=#{modifiedUser}, modified_time=#{modifiedTime} where uid = #{uid} </update > <select id ="findByUid" resultMap ="User" > select * from stores..t_user where uid=#{uid} </select >
做单元测试功能测试
1 2 3 4 5 6 7 8 9 10 11 @Test public void updatePasswordByUid () { Integer i = userMapper.updatePasswordByUid(3 , "321" , "系统管理员" , new Date()); System.out.println(i); } @Test public void findByUid () { User byUid = userMapper.findByUid(3 ); System.out.println(byUid); }
2. 修改密码—业务层 2.1规划异常 1.用户的原密码错误,is_delete==1、uid找不到,归结在用户没有发现的异常
2.update在更新的时候,可能产生未知的异常,UpdateException。
2.2 设计接口的抽象方法 执行用户修改吗密码的核心方法。
1 2 3 4 void changePassword (Integer uid, String username, String olePassword, String newPassword) ;
在实现类中实现当前的抽象方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public void changePassword (Integer uid, String username, String olePassword, String newPassword) { User result = userMapper.findByUid(uid); if (result==null || result.getIsDelete()==1 ){ throw new UserNotFoundException("用户数据不存在" ); } String oldMd5Password = gitMD5Password(olePassword, result.getSalt()); if (!result.getPassword().equals(oldMd5Password)){ throw new PasswordNotMatchException("密码错误" ); } String newMd5Password = gitMD5Password(newPassword, result.getSalt()); Integer rows = userMapper.updatePasswordByUid(uid, newMd5Password, username, new Date()); if (rows==1 ){ throw new UpdateException("更新数据时,产生位置的异常" ); } }
在单元测试类中编写测试方法
1 2 3 4 @Test public void changePassword () { IUserService.changePassword(4 ,"text02" ,"123" ,"321" ); }
3. 修改密码-控制层 3.1 处理异常 UpdateException需要配置在统一的异常处理的方法中。
1 2 3 4 else if (e instanceof UpdateException) { result.setState(5001 ); result.setMessage(e.getMessage()); }
3.2 设计请求 1 2 3 4 /users/change_ password post String oldPassword,String newPassword,HttpSession session //需要和表单中的name属性值保持一致 JsonResult<Void>
3.3 处理请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RequestMapping("change_password") public JsonResult<Void> changePassword (String oldPassword, String newPassword, HttpSession session) { Integer uid = getuidFromSession(session); String username = getUsernameFromSession(session); userService.changePassword(uid,username,oldPassword,newPassword); return new JsonResult<>(OK,"更新成功" ); }
4. 修改密码-前端页面 password.html 中添加ajax请求的数据,不在手动去编写ajax结构,直接复制,然后在微调修改参数即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script type="text/javascript" > $("#btn-change-password" ).click(function ( ) { $.ajax({ url : "/users/change_password" , type : "POST" , data : $("#form-change-password" ).serialize(), dataType : "JSON" , success : function (JSON ) { if (JSON .state==200 ){ alert(JSON .message); }else { alert(JSON .message); } }, error : function (xhr ) { alert("修改密码时产生未知的异常" +xhr.message) } }) }) </script>
个人资料 1. 个人资料-持久层 1.根据用户信息的SQL语句
1 update t_user set phone= ?,email= ?,gender= ?,modified_user= ?,modified_time= ? where uid= ?
2.根据用户名查询用户的数据。
1 select * from t_user where uid= ?
查询用户的数据不需要再重复开发。
1.2 接口与抽象方法 更新用户的信息方法的定义。
1 2 3 4 5 6 Integer updateInfoByUid (User user) ;
1.3 抽象方法的映射 在UserMapper.xml文件中进行映射的编写。
1 2 3 4 5 6 7 8 9 10 11 12 <update id ="updateInfoByUid" > update stores.t_user set <if test ="phone!=null" > phone=#{phone},</if > <if test ="email!=null" > email=#{email},</if > <if test ="gender!=null" > gender=#{gender},</if > modified_user=#{modifiedUser}, modified_time=#{modifiedTime} where uid = #{uid} </update >
在测试类中完成功能测试
1 2 3 4 5 6 7 8 9 @Test public void updateInfoByUid () { User user = new User(); user.setUid(4 ); user.setPhone("13619284567" ); user.setEmail("289028@qq.com" ); user.setGender(1 ); userMapper.updateInfoByUid(user); }
2. 个人资料-业务层 2.1 异常规划 1.设计两个功能:
当打开页面是获取用户的信息并且填充到对应的文本框中
检测用户是否点击了修改按钮,如果检测到则执行修改用户信息的操作。
2.打开页面的时候可能找不到用户的数据,点击删除按钮之前需要再次的去检测用户我的数据是否存在。
2.2 接口和抽象的方法 主要有两个功能的模块,对应的是两个抽象的方法的设计。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 User getByUid (Integer uid) ;void changeInfo (Integer uid,String username,User user) ;
2.3 实现抽象方法 在UserSerivceImpl类中添加两个抽象方法的具体实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Override public User getByUid (Integer uid) { User result = userMapper.findByUid(uid); if (result.getIsDelete()==1 || result==null ){ throw new UserNotFoundException("用户数据不存在" ); } User user = new User(); user.setUsername(result.getUsername()); user.setPhone(result.getPhone()); user.setEmail(result.getEmail()); user.setGender(result.getGender()); return user; } @Override public void changeInfo (Integer uid, String username, User user) { User result = userMapper.findByUid(uid); if (result.getIsDelete()==1 || result==null ){ throw new UserNotFoundException("用户数据不存在" ); } user.setUid(uid); user.setModifiedUser(username); user.setModifiedTime(new Date()); Integer rows = userMapper.updateInfoByUid(user); if (rows!=1 ){ throw new UpdateException("更新数据时,产生位置的异常" ); } }
在测试类中进行功能单元测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void getByUid () { System.out.println(IUserService.getByUid(4 )); } @Test public void changeInfo () { User user=new User(); user.setPhone("13600000000" ); user.setEmail("amh@qq.com" ); user.setGender(0 ); IUserService.changeInfo(4 ,"管理员" ,user); }
3. 个人资料-控制层 3.1 处理异常
暂无
3.2 设计请求 1.设置一打开页面就发送当前用户数据的查询。
1 2 3 4 /users/get_ by_ uid GET HttpSession session JsonResult<User>
2.点击修改按钮发送用户的数据修改操作请求的设计。
1 2 3 4 /user.change_ info POST User user,HttpSession session JsonResult<Void>
3.3处理请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RequestMapping("get_by_uid") public JsonResult<User> getByUid (HttpSession session) { User data = userService.getByUid(getuidFromSession(session)); return new JsonResult<>(OK,data); } @RequestMapping("change_info") public JsonResult<Void> changeInfo (User user,HttpSession session) { Integer uid = getuidFromSession(session); String username = getUsernameFromSession(session); userService.changeInfo(uid,username,user); return new JsonResult<>(OK,"更新成功" ); }
4. 个人资料-前端页面 1.在打开userdata.html页面自动发送ajax请求,查询到的数据填充到这个页面。
2.在检测到用户点击了修改按钮之后发送一个ajax请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <script type="text/javascript" > $(document ).ready(function ( ) { $.ajax({ url : "/users/get_by_uid" , type : "GET" , data : $("#form-change-info" ).serialize(), dataType : "JSON" , success : function (JSON ) { if (JSON .state==200 ){ $("#username" ).val(JSON .data.username); $("#phone" ).val(JSON .data.phone); $("#email" ).val(JSON .data.email); let radio = JSON .data.gender ==0 ? $("#gender-female" ) : $("#gender-male" ) radio.prop("checked" ,"checked" ); }else { alert(JSON .message); } }, error : function (xhr ) { alert("查询信息时产生未知的异常" +xhr.message) } }) }) $("#btn-change-info" ).click(function ( ) { $.ajax({ url : "/users/change_info" , type : "POST" , data : $("#form-change-info" ).serialize(), dataType : "JSON" , success : function (JSON ) { if (JSON .state==200 ){ alert(JSON .message); location.href="userdata.html" ; }else { alert(JSON .message); } }, error : function (xhr ) { alert("更新个人数据时产生未知的异常" +xhr.message) } }) }) </script>
上传头像 1.上传头像-持久层 1.1 SQL语句的规划 将对象文件保存在操作系统上,然后在把这个文件路径记录下来,因为在记录路径的是非常便捷和方便的,如果要打开这个文件可以依据这个路径去找到这个文件。在数据库中需要保存这个文件的路径即可。将所有的静态资源(图片、文件、其他资源文件)放到某台电脑上,在把这个电脑作为一台单独的服务器使用。
对应的是一个更新用户avatar字段的sql语句。
1 update t_user set avatar= ?,modified_user= ?,modified_time= ? where uid= ?
1.2 设计接口和抽象方法 UserMapper接口中来定义个抽象方法用于修改用户的头像。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Integer updateAvatarByUid(@Param ("uid") Integer uid, @Param ("avatar") String avatar, @Param ("modifiedUser") String modifiedUser, @Param ("modifiedTime") Date modifiedTime);
在测试类中进行功能单元测试。
1 2 3 4 5 @Test public void updateAvatarByUid () { userMapper.updateAvatarByUid(4 , "/upload/avatar.png" , "管理员" , new Date()); }
2. 上传头像-业务层 2.1 规划异常 1.用户数据不存在,找不到对应的用户数据
2.更新的时候,各种未知的异常产生。
无需重复开发
2.2 设计接口和抽象方法 编写业务层的更新用户头像的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public void changeAvatar (Integer uid, String avatar, String username) { User result = userMapper.findByUid(uid); if (result.getIsDelete()==1 || result==null ){ throw new UserNotFoundException("用户数据不存在" ); } Integer rows = userMapper.updateAvatarByUid(uid, avatar, username, new Date()); if (rows != 1 ){ throw new UpdateException("更新头像时,产生未知的异常" ); } }
在测试类中进行功能单元测试。
1 2 3 4 @Test public void changeAvatar () { IUserService.changeAvatar(4 ,"/upload/test.png" ,"小明" ); }
3.上传头像-控制层 3.1 规划异常 1 2 3 4 5 6 7 8 9 文件异常的父类: FileUploadException 泛指文件上传异常(父类)继承 RuntimeException 父类是:FileUploadException FileEmptyException 文件为空的异常 FileSizeException 文件大小超出限制 FileTypeException 文件类型异常 FileUploadIOException 文件读写的异常 FileStateException 文件读写的异常
五个规则方法显示的声明出来,再去继承相关的父类
3.2 处理异常 在基类BaseContoller类中进行编写和统一处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 else if (e instanceof FileEmptyException) { result.setState(6000 ); result.setMessage(e.getMessage()); }else if (e instanceof FileSizeException) { result.setState(6001 ); result.setMessage(e.getMessage()); }else if (e instanceof FileTypeException) { result.setState(6002 ); result.setMessage(e.getMessage()); }else if (e instanceof FileStateException) { result.setState(6003 ); result.setMessage(e.getMessage()); }else if (e instanceof FileUploadIOException) { result.setState(6004 ); result.setMessage(e.getMessage()); }
在异常统一处理的参数列表上增加新的异常处理作为它的参数。
1 @ExceptionHandler({ServiceException.class,FileUploadException.class})
3.3 设计请求 1 2 3 4 /users/change_ avatar POST(get请求提交数据2KB) HttpSession session, MultipartFile file JsonResult<String>
3.4 实现请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 public static final int AVATAR_MAX_SIZE = 10 *1024 *1024 ;public static final List<String> AVATAR_TYPE=new ArrayList<>();static { AVATAR_TYPE.add("images/jpeg" ); AVATAR_TYPE.add("images/png" ); AVATAR_TYPE.add("images/bmp" ); AVATAR_TYPE.add("images/gif" ); } @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("文件类型不支持" ); } String parent = "E:\\电脑商城\\store\\src\\main\\resources\\static\\upload\\" ; File dir = new File(parent); if (!dir.exists()){ dir.mkdirs(); } String originalFilename = file.getOriginalFilename(); int index =originalFilename.lastIndexOf("." ); String suffix =originalFilename.substring(index); String filename= UUID.randomUUID().toString().toUpperCase()+suffix; File dest =new File(dir,filename); try { file.transferTo(dest); }catch (FileStateException e) { throw new FileStateException("文件状态异常" ); } catch (IOException e) { throw new FileUploadIOException("文件读写异常" ); } Integer uid = getuidFromSession(session); String username = getUsernameFromSession(session); String avatar="/upload" +filename; userService.changeAvatar(uid,avatar,username); return new JsonResult<>(OK,avatar); }
4.上传头像-前端页面 在upload页面中编写上传头像的代码
说明:如果直接使用表单进行文件的上传,需要给表单显示的添加一个属性声明enctype=”multipart/form-data” 出来,不会将目标文件的数据结构做修改在上传,不同字符串
1 2 3 <form method ="post" action ="/change_avatar" enctype ="multipart/form-data" class ="form-horizontal" role ="form" >
并且按钮是type=”submit”
1 <input id ="btn-change-avatar" type ="submit" class ="btn btn-primary" value ="上传" />
(推荐) 如果使用Ajax来进行文件的上传,则不需要给表单添加以上数据 (推荐)
只需要给 form标签 和 按钮(type=”button”)加id值即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <script type="text/javascript" > $("#btn-change-avatar" ).click(function ( ) { $.ajax({ url : "/users/change_avatar" , type : "POST" , data : new FormData($("#form-change-avatar" )[0 ]), processData : false , contentType : false , dataType : "JSON" , success : function (JSON ) { if (JSON .state==200 ){ alert("头像修改成功" ); $("#img-avatar" ).attr("src" ,JSON .data); $.cookie("avatar" ,JSON .data,{expires : 7 }) }else { alert("头像修改失败" ); } }, error : function (xhr ) { alert("更新个人数据时产生未知的异常" +xhr.message) } }) }) </script>
5. 解决Bug 5.1.更改默认的大小限制 SpringMVC默认为1MB网站可以进行上传,手动的去修改SpringMVC默认上传文件的大小。
方式1:直接可以在配置文件中配置:
1 2 3 4 5 servlet: multipart: max-file-size: 10MB max-request-size: 15MB
方式2:需要采用java代码的进行来设置文件的上传大小的限制,主类中进行配置,可以定义一个方法,必须使用@Bean修饰符来修饰。在类的前面添加一个@Configration注解进行修饰类。
1 2 3 4 5 6 7 8 9 10 11 12 @Bean public MultipartConfigElement getMultipartConfigElement () { MultipartConfigFactory factory = new MultipartConfigFactory(); factory.setMaxFileSize(DataSize.of(10 , DataUnit.MEGABYTES)); factory.setMaxRequestSize(DataSize.of(15 , DataUnit.MEGABYTES)); return factory.createMultipartConfig(); }
5.2 显示头像 在页面中通过ajax请求来提交文件,提交完成后返回json串,解析出data中数据,设置到img头像的标签的src属性上就可以了。
serialize():可以将表单中的数据自动拼接成key=value的结果进行提交给服务器,一般提交的是普通的控件类型中的数据(text\password\radio\checkbox)等等
FormData类:将表单中数据保持原有的结果进行数据的提交。
1 new FormData($("#from" )[0 ]);
ajax默认处理数据时按照字符串的形式进行处理,以及默认会采用字符串的形式进行提交数据。关闭这两个默认的功能
5.3 登录后显示头像 可以更新头像成功后,将服务器返回的头像路径保存到客户端的Cookie对象,然后每次检测到用户打开上传头像页面,在这个页面中通过ready()方法来自动检测去读取Cookie中头像并设置到src属性上。
1.设置cookie中的值:
导入cookie.js文件
1 <script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8" ></script>
调用cookie方法:
1 $.cookie(key,value,time)
2.在login.html 页面先引入cookie.js文件
1 <script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8" ></script>
3.在登录时就在cookie中存入用户自身的avatar 路径
1 2 $.cookie("avatar" ,JSON .data.avatar,{expires : 7 })
4.在updata.html 页面先引入cookie.js文件
1 <script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8" ></script>
5.在updata.html 页面通过ready()自动读取cookie中的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 <script type="text/javascript" > $(document ).ready(function ( ) { let avatar=$.cookie("avatar" ); $("#img-avatar" ).attr("src" ,avatar); }); </script>
5.4 解决上传图片不能显示的问题
spring boot图片上传回显问题—上传后重启项目或者多次刷新才显示图片
原因:因为对服务器的保护措施导致的,服务器不能对外部暴露真实的资源路径,需要配置虚拟路径映射访问。
解决方法:
新增收获地址 1.新增收货地址-数据表的创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 新增收货地址-创建实体类 创建com.amh.entity.Address新增收获地址的实体类,继承自BaseEntity类,在类中声明与数据表中对应的属性,添加Getters and Setters方法,基于唯一标识aid生成hashCode()和equals()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.amh.entity;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.io.Serializable;@Data @AllArgsConstructor @NoArgsConstructor public class Address extends BaseEntity implements Serializable { 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 新增收货地址-持久层 3.1 各种功能的开发顺序 当前收货地址功能模块:列表的展示、修改、删除、设置默认、新增收获地址。开发顺序:新增收获地址-列表展示-设置默认收获地址-删除收货地址-修改收货地址。
3.2 规划需要执行的SQL语句 1.对应是插入语句
1 insert into t_address (除了aid外字段列表) values (字段值列表)
2.一个用户的收货地址规定最多只能有20条数据对应。在插入用户数据之前先做查询操作。收获地址逻辑控制方面的一个异常。
1 select count (* ) t_address where uid= ?
3.3 接口与抽象方法 1.创建一个接口AddressMapper,在这个接口中来定义上面两个SQL语句抽象方法定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.amh.mapper;import com.amh.entity.Address;public interface AddressMapper { Integer insert (Address address) ; Integer countByUid (Integer uid) ; }
3.4 配置SQL映射
创建一个AddressMapper.xml映射文件,在这个文件中添加抽象方法的映射,映射到的sql语句上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?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.amh.mapper.AddressMapper" > <resultMap id ="AddressEntityMap" type ="com.amh.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_default" property ="isDefault" /> <result column ="created_user" property ="createdUser" /> <result column ="created_time" property ="createdTime" /> <result column ="modified_user" property ="modifiedUser" /> <result column ="modified_time" property ="modifiedTime" /> </resultMap > <insert id ="insert" useGeneratedKeys ="true" keyProperty ="aid" > INSERT INTO stores.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 stores.t_address WHERE uid = #{uid} </select > </mapper >
2.在test下的mapper文件夹下创建AddressMapperTests的测试类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @RunWith(SpringRunner.class) public class AddressMapperTests { @Autowired(required = false) private AddressMapper addressMapper; @Test public void insert () { Address address=new Address(); address.setUid(6 ); address.setPhone("12345678911" ); address.setName("安" ); addressMapper.insert(address); } @Test public void countByUid () { Integer count = addressMapper.countByUid(6 ); System.out.println(count); } }
4.新增收货地址-业务层 4.1 规划异常 如果用户是第一次插入用户的收货地址,规则:当前用户插入的地址是第一条时,需要将当前地址作为默认的收货地址。如果查询的总数为0,则将当前地址的is_default值设置为1。查询条件的结果为0不代表异常。
查询到的结果大于20了,这时候需要抛出业务控制的异常AddressCountLimitException异常。自行创建这个异常。
1 2 3 4 public class AddressCountLimitException extends ServiceException { }
插入数据时产生未知的异常InsertException,不需要再重复的创建。
4.2 接口与抽象方法 1.创建一个IAddressService接口。在中定义业务的抽象方法。
1 2 3 4 5 6 7 8 9 10 package com.amh.service;import com.amh.entity.Address;public interface IAddressService { void addNewAddress (Integer uid, String username, Address address) ; }
2.创建一个AddressServiceImpl实现类,去实现接口中抽象方法。
在配置文件中定义数据。
1 2 # Spring读取配置文件中数据:@Value("${user.address.max-count}") user.address.max-count: 20
在实现类中显示业务控制。
4.3 实现抽象方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.amh.service.impl;import com.amh.entity.Address;import com.amh.mapper.AddressMapper;import com.amh.service.IAddressService;import com.amh.service.ex.AddressCountLimitException;import com.amh.service.ex.InsertException;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import java.util.Date;public class AddressServiceImpl implements IAddressService { @Autowired private AddressMapper addressMapper; @Value("${user.address.max-count}") private Integer maxCount; @Override public void addNewAddress (Integer uid, String username, Address address) { Integer count = addressMapper.countByUid(uid); if (count > maxCount) { throw new AddressCountLimitException("用户收货地址超出上限" ); } address.setUid(uid); Integer isDefault = count == 0 ? 1 : 0 ; address.setIsDefault(isDefault); 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.测试业务层的功能是否正常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RunWith(SpringRunner.class) public class AddressServiceTest { @Autowired(required = false) private IAddressService addressService; @Test public void addNewAddress () { Address address=new Address(); address.setPhone("4567898945" ); address.setName("梦赫" ); addressService.addNewAddress(1 ,"安梦赫" ,address); } }
5.新增收货地址-控制层 5.1 处理异常 业务层抛出了收货地址总数超标的异常,在BaseController中进行处理。
1 2 3 4 else if (e instanceof AddressCountLimitException) { result.setState(4003 ); result.setMessage(e.getMessage()); }
5.2 设计请求 1 2 3 4 /addresses/add_ new_ address post Address address,HttpSession session JsonResult<Void>
5.3 处理请求 在控制层创建AddressController来处理用户收货地址的请求和响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.amh.controlle;import com.amh.entity.Address;import com.amh.service.IAddressService;import com.amh.util.JsonResult;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpSession;@RestController @RequestMapping("addresses") public class AddressController extends BaseController { @Autowired private IAddressService addressService; @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/addresses/add_new_address?name=Tom&phone=136192
6 新增收货地址-前端页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <script type="text/javascript" > $("#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(json.message); }else { alert(json.message); } }, error :function (xhr ) { alert("新增收货地址产生未知的错!" +xhr.status) } }); }); </script>
获取省/市/区的列表 1.获取省/市/区的列表-数据表的创建 1 2 3 4 5 6 7 create table `t_dict_district` ( `id` int (11 ) NOT NULL AUTO_INCREMENT, `parent` varchar (18 ) DEFAULT NULL , `code` varchar (18 ) DEFAULT NULL , `name` varchar (48 ) DEFAULT NULL , PRIMARY KEY ('id' ) ); ENGINE= InnoDB DEFAULT CHARSET= utf8;
parent属性表示的是父区域代码号,省的父代码号是+86
2.获取省/市/区的列表-创建实体类 创建一个District的实体类。
1 2 3 4 5 6 7 8 9 10 11 12 @Data @AllArgsConstructor @NoArgsConstructor public class District implements Serializable { private Integer id; private String parent; private String code; private String name; }
3.获取省/市/区的列表-持久层 3.1 规划需要执行的SQL语句 获取全国所有省/某省所有市/某市所有区的查询SQL语句大致是:
查询语句,根据父代号进行查询。
1 select * from t_dict_district where parent= ? order by code ASC ;
3.2 接口与抽象方法 创建DistrictMapper接口,添加抽象方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.amh.mapper;import com.amh.entity.District;import java.util.List;public interface DistrictMapper { List<District> findByParent (String parent) ; }
3.3 配置SQL映射 1.创建DistrictMapper.xml,修改根节点的namespace属性的值为以上接口文件,并配置以上抽象方法的映射。
1 2 3 4 5 6 7 8 9 10 11 <select id ="findByParent" resultType ="com.amh.entity.District" > SELECT * FROM stores.t_dict_district WHERE parent=#{parent} ORDER BY code ASC </select >
2.编写测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @SpringBootTest @RunWith(SpringRunner.class) public class DistrictMapperTests { @Autowired(required = false) private DistrictMapper districtMapper; @Test public void findByParent () { String parent = "110100" ; List<District> list = districtMapper.findByParent(parent); for (District district : list) { System.out.println(district); } } }
4.获取省/市/区的列表-业务层 4.1 规划异常
说明 :无异常。
4.2 接口与抽象方法 创建IDistrictService接口,并添加抽象方法。
1 2 3 4 5 6 7 8 public interface IDistrictService { List<District> getByParent (String parent) ; }
4.3 实现抽象方法 1.创建DistrictServiceImpl类,实现IDistrictService接口,在类之前添加@Service注解,以及在类中添加持久层对象并使用@Autowired修饰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Service public class IDistrictServiceImpl implements IDistrictService { @Autowired DistrictMapper districtMapper; @Override public List<District> getByParent (String parent) { List<District> list = districtMapper.findByParent(parent); for (District district : list) { district.setId(null ); district.setParent(null ); } return list; } }
2.单元测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @SpringBootTest @RunWith(SpringRunner.class) public class DistrictServiceTests { @Autowired(required = false) private IDistrictService districtService; @Test public void getByParent () { String parent = "86" ; List<District> list = districtService.getByParent(parent); System.out.println("count=" + list.size()); for (District item : list) { System.out.println(item); } } }
5.获取省/市/区的列表-控制层 5.1 处理异常
说明 :无异常。
5.2 设计请求 设计用户提交的请求,并设计响应的方式。
请求路径:/districts/
请求参数:String parent
请求类型:GET
响应结果:JsonResult<List<District>>
是否拦截:否,需要在拦截器的配置中添加白名单
5.3 处理请求 创建DistrictController,继承自BaseController类,在类之前添加@RequestMapping(“districts”)和@RestController注解,并在类中添加业务层对象,对其使用@Autowired注解修饰。
在这个类中来编写处理请求的方法
1 2 3 4 5 6 7 @RequestMapping("districts") @RestController public class DistrictController extends BaseController { @Autowired private IDistrictService districtService; }
2.在类中添加处理请求的方法getByParent(String parent)及方法的实现。
@GetMapping:是一个组合注解,等价于@RequestMapping(method={RequestMethod.GET}),它将HTTP的GET请求映射到特定的处理方法上。“/”表示方法将处理所有传入的URI请求。简化代码。
1 2 3 4 5 @GetMapping({"", "/"}) public JsonResult<List<District>> getByParent(String parent) { List<District> data = districtService.getByParent(parent); return new JsonResult<>(OK, data); }
3.在拦截器LoginInterceptorConfigurer类的addInterceptors(InterceptorRegistry registry)方法中将“districts”请求添加为白名单。如果已经添加无需重复添加。
1 patterns.add("/districts/**" );
4.完成后启动项目,打开浏览器(不需要登录),直接访问http://localhost:8080/districts?parent=86进行测试。
6.获取省/市/区的列表-前端页面 1.在addAddress.html页面中的head标签内导入的distpicker.data.js和distpicker.js文件注释掉。
2.在新增收货地址表单中,给”选择省”控件添加name=”provinceCode”和id=”province-list”属性,给”选择市”添加name=”cityCode”和id=”city-list”属性,给”选择区”控件添加name=”areaCode”和id=”area-list”属性。以上属性如果已经添加无需重复添加。
3.运行前端看是否还可以正常保存数据(除了省市区之外)
获取省市区的名称 1. 获取省市区的名称-持久层 1.获取根据当前code来获取当前省市区的名称,对应就是一条查询语句。
1 select * from t_dist_district where code= ?
2.在DistrictMapper接口定义出来。
1 String findNameByCode (String code) ;
3.在DistrictMapper.xml文件中添加抽象方法的映射。
1 2 3 <select id ="findNameByCode" resultType ="java.lang.String" > select * from stores.t_dict_district where code=#{code} </select >
单元测试
1 2 3 4 5 @Test public void findNameByCode () { String nameByCode = districtMapper.findNameByCode("610000" ); System.out.println(nameByCode); }
2.获取省市区的名称-业务层 1.在业务层没有异常需要处理。
2.定义对应的业务层接口中的抽象方法。
1 2 3 4 5 6 String getNameByCode (String code) ;
3.在子类中实现
1 2 3 4 @Override public String getNameByCode (String code) { return districtMapper.findNameByCode(code); }
4.测试可以省略不写(超过8行以上的代码都要进行独立的测试)。
3.获取省市区的名称-业务层优化 1.添加地址层依赖于IDistrictService层。
1 2 3 @Autowired private IDistrictService districtService;
2.在addNewAddress方法中将districtService接口中获取到的省市区数据转移到address对象,这个对象中就包括了所有的用户收货地址的数据。
1 2 3 4 5 6 7 String provinceName = districtService.getNameByCode(address.getProvinceCode()); String cityName = districtService.getNameByCode(address.getCityCode()); String areaName = districtService.getNameByCode(address.getAreaCode()); address.setProvinceName(provinceName); address.setCityName(cityName); address.setAreaName(areaName);
4 获取省市区-前端页面 1.addAddress.html页面中来编写对应的省市区展示,根据用户的不同选择来限制对应的标签中的内容。
2.编写相关的事件代码。
要理解下面代码,必须先了解数据表的数据之间的关系(以下代码并不难)
t_dict_district表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 <script type="text/javascript" > let defaultOption="<option value='0'>---- 选择区 ----</option>" $(document ).ready(function ( ) { showProvinceList(); $("#city-list" ).append(defaultOption); $("#area-list" ).append(defaultOption); }); $("#city-list" ).change(function ( ) { let parent = $("#city-list" ).val(); console .log(parent); $("#area-list" ).empty(); $("#area-list" ).append(defaultOption); if (parent==0 ){ return ; } $.ajax({ url :"/districts/" , type :"GET" , data : "parent=" +parent, dataType :"JSON" , success :function (json ) { if (json.state==200 ){ let list=json.data; for (let i=0 ;i<list.length;i++){ let opt="<option value='" +list[i].code+"'>" +list[i].name+"</option>" $("#area-list" ).append(opt); } }else { alert("县区信息加载数据失败" ); } } }); }) $("#province-list" ).change(function ( ) { let parent = $("#province-list" ).val(); console .log(parent); $("#city-list" ).empty(); $("#area-list" ).empty(); $("#city-list" ).append(defaultOption); $("#area-list" ).append(defaultOption); if (parent==0 ){ return ; } $.ajax({ url :"/districts/" , type :"GET" , data : "parent=" +parent, dataType :"JSON" , success :function (json ) { if (json.state==200 ){ let list=json.data; for (let i=0 ;i<list.length;i++){ let opt="<option value='" +list[i].code+"'>" +list[i].name+"</option>" $("#city-list" ).append(opt); } }else { alert("城市信息加载数据失败" ); } } }); }) function showProvinceList ( ) { $.ajax({ url :"/districts/" , type :"GET" , data : "parent=86" , dataType :"JSON" , success :function (json ) { if (json.state==200 ){ let list=json.data; for (let i=0 ;i<list.length;i++){ let opt="<option value='" +list[i].code+"'>" +list[i].name+"</option>" $("#province-list" ).append(opt); } }else { alert("省/直辖市信息加载数据失败" ); } } }); }