当前位置: 首页 > news >正文

北京网站开发工程师招聘网重庆市工程新希望官网

北京网站开发工程师招聘网,重庆市工程新希望官网,拼多多标题优化软件,怎么上传图片到公司网站计算机毕业设计|基于SpringBootMyBatis框架的电脑商城的设计与实现#xff08;用户上传头像#xff09; 该项目分析着重于设计和实现基于SpringBootMyBatis框架的电脑商城。首先#xff0c;通过深入分析项目所需数据#xff0c;包括用户、商品、商品类别、收藏、订单、购物…计算机毕业设计|基于SpringBootMyBatis框架的电脑商城的设计与实现用户上传头像 该项目分析着重于设计和实现基于SpringBootMyBatis框架的电脑商城。首先通过深入分析项目所需数据包括用户、商品、商品类别、收藏、订单、购物车、收货地址建立了数据模型。关于SpringBootMyBatis框架的电脑商城的设计与实现我会按照系统概述与环境搭建、用户注册登录、用户资料修改、用户上传头像、-用户收货管理、商品、购物车、订单、AOP的顺序依次更新。本文内容承接前面博客内容主要是项目用户上传头像用户收货管理。 1 MultipartFile接口 MultipartFile接口常用的的API见下表 方法功能描述String getOriginalFilename()获取上传文件的原始文件名即该文件在客户端中的文件名boolean isEmpty()判断上传的文件是否为空当没有选择文件就直接上传或者选中的文件是0字节的空文件时返回true否则返回falselong getSize()获取上传的文件大小以字节为单位String getContentType()根据所上传的文件的扩展名决定该文件的MIME类型例如上传.jpg格式的图片将返回image/jpegInputStream getInputStream()获取上传文件的输入字节流通常用于自定义读取所上传的文件的过程该方法与transferTo()方法不可以同时使用void transferTo(File dest)保存上传的文件该方法与getInputStream()方法不可以同时使用 2 MultipartResolver接口 1.MultipartResolver可以将上传过程中产生的数据封装为MultipartFile类型的对象中。 2.在配置MultipartResovler时可以为其中的几个属性注入值 maxUploadSize上传文件的最大大小假设设置值为10M一次性上传5个文件则5个文件的大小总和不允许超过10M。maxUploadSizePerFile每个上传文件的最大大小假设设置值为10M一次性上传5个文件则每个文件的大小都不可以超过10M但是5个文件的大小总和可以接近50M。defaultEncoding默认编码。 3 基于SpringMVC的文件上传案例 3.1 创建项目 1.创建Java Enterprise项目设置Name为springmvc-uploadGroup为com.cyArtifact为controller的Java企业级项目。 2.将项目com.cy.controller包下自动生成的HelloServlet类删除并删除webapp下自动生成的index.jsp文件。 3.添加文件上传jar包依赖关于文件上传需要添加spring-webmvc和commons-fileupload依赖。 dependenciesdependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion4.3.6.RELEASE/version/dependency!-- 文件上传 --dependencygroupIdcommons-fileupload/groupIdartifactIdcommons-fileupload/artifactIdversion1.4/version/dependency /dependencies4.在src\main\resources文件夹下创建spring配置文件并将文件命名为spring-upload.xml。 ?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beansxmlns:contexthttp://www.springframework.org/schema/contextxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.3.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.3.xsd/beans3.2 前端页面设计 在webapp目录下创建upload.html页面并在页面中添加如下代码。 !DOCTYPE html html langen headmeta charsetUTF-8title文件上传/title /head bodyh3文件上传/h3!-- enctype属性规定表单中数据在提交给服务器之前如何进行编码。默认表单数据的编码是application/x-www-form-urlencoded。application/x-www-form-urlencoded提交前表单中所有数据都会进行编码编码的规则是空格转换为加号特殊符号转换为ASCII HEX值text/plain提交前表单中数据空格转换为加号但不对特殊字符进行编码。multipart/form-data提交前表单中不对字符进行编码在使用包含文件上传控件的表单中必须使用该值--form actionupload.do methodpost enctypemultipart/form-datatable border1 cellspacing0 cellpadding0trtd文件名(N)/tdtdpinput typefile namefile//p/td/trtrtd colspan2 aligncenterinput typesubmit name上传//td/tr/table/form /body /html注意form表单的请求方式必须设置为POST并配置属性enctype“multipart/form-data”文件上传input控件的name属性值需设置为file值。 3.3 后台功能实现 1.在web.xml文件中配置前端控制器和过滤器并指定DispatcherServlet加载的配置文件springmvc-upload.xml的位置。 servletservlet-namespringmvc/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classinit-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:springmvc-upload.xml/param-value/init-paramload-on-startup1/load-on-startup /servlet servlet-mappingservlet-namespringmvc/servlet-nameurl-pattern*.do/url-pattern /servlet-mappingfilterfilter-nameCharacterEncodingFilter/filter-namefilter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-classinit-paramparam-nameencoding/param-nameparam-valueutf-8/param-value/init-param /filter filter-mappingfilter-nameCharacterEncodingFilter/filter-nameurl-pattern/*/url-pattern /filter-mapping2.创建com.cy.controller.UploadController控制器类在类的声明之前添加Controller注解并在控制器中添加处理请求的upload()方法为此方法添加类型为MultipartFile接口的参数并为该参数添加RequestParam注解表示客户端上传的文件。 package com.cy.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; import java.util.UUID;Controller public class UploadController {/*RequestMapping(upload.do)ResponseBodypublic String upload(RequestParam(file) MultipartFile file) {System.out.println(UploadController.upload()...);File dest new File(D:/1.png);try {// 调用MultipartFile参数对象的transferTo()方法即可保存上传的文件file.transferTo(dest);} catch (IOException e) {e.printStackTrace();}return OK;}*/RequestMapping(upload.do)ResponseBodypublic String upload(HttpServletRequest request, RequestParam(file) MultipartFile file) throws IOException {// 获取上传文件的原始文件名String originalFilename file.getOriginalFilename();// 获取上下文的绝对路径String realPath request.getServletContext().getRealPath(upload);System.out.println(realPath);// 创建File文件对象File dir new File(realPath);if (!dir.exists()) {dir.mkdirs();}// 自定义上传文件名String fileName UUID.randomUUID().toString();// 获取上传文件扩展名String suffix ;int beginIndex originalFilename.lastIndexOf(.);if (beginIndex 0) {suffix originalFilename.substring(beginIndex);}String fullFilename fileName suffix;// 调用MultipartFile参数对象的transferTo()方法即可保存上传的文件file.transferTo(new File(dir, fullFilename));return OK;} }3.在springmvc-upload.xml配置文件中添加组件扫描和CommonsMultipartResolver类的bean标签配置。 !-- 组件扫描 -- context:component-scan base-packagecom.cy /!-- CommonsMultipartResolver -- bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver/bean注意CommonsMultipartResolver类在配置时id值必须设置成multipartResolver。 4.启动项目访问http://localhost:8080/springmvc_upload_war_exploded/upload.html网址测试文件提交。 上传头像 1 用户-上传头像-持久层 1.1 规划需要执行的SQL语句 上传文件的操作其实是先将用户上传的文件保存到服务器端的某个位置然后将保存文件的路径记录在数据库中。当后续需要使用该文件时从数据库中读出文件的路径即可实现在线访问该文件。 在持久层处理数据库中的数据时只需要关心如何记录头像文件的路径并不需要考虑上传时保存文件的过程。所以需要执行的SQL语句大致是 update t_user set avatar?, modified_user?, modified_time? where uid?1.2 接口与抽象方法 在UserMapper接口中添加updateAvatarByUid()抽象方法。 /*** 根据uid更新用户的头像* param uid 用户的id* 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 配置SQL映射 1.在UserMapper.xml中配置updateAvatarByUid()抽象方法的映射。 !-- 根据uid更新用户的头像Integer updateAvatarByUid(Param(uid) Integer uid,Param(avatar) String avatar,Param(modifiedUser) String modifiedUser,Param(modifiedTime) Date modifiedTime) -- update idupdateAvatarByUidUPDATEt_userSETavatar #{avatar},modified_user #{modifiedUser},modified_time #{modifiedTime}WHEREuid #{uid} /update2.在UserMapperTests中编写并执行单元测试。 Test public void updateAvatarByUid() {Integer uid 20;String avatar /upload/avatar.png;String modifiedUser 超级管理员;Date modifiedTime new Date();Integer rows userMapper.updateAvatarByUid(uid, avatar, modifiedUser, modifiedTime);System.out.println(rows rows); }2 用户-上传头像-业务层 2.1 规划异常 在修改头像值前先检查用户数据状态可能抛UserNotFoundException异常由于最终执行的是修改操作还可能抛UpdateException异常。 2.2 接口与抽象方法 在IUserService中添加changeAvatar(Integer uid, String username, String avatar)抽象方法。 /*** 修改用户头像* param uid 当前登录的用户的id* param username 当前登录的用户名* param avatar 用户的新头像的路径*/ void changeAvatar(Integer uid, String username, String avatar);2.3 实现抽象方法 1.在UserServiceImpl类中实现changeAvatar(Integer uid, String username, String avatar)方法。 Override public void changeAvatar(Integer uid, String username, String avatar) {// 调用userMapper的findByUid()方法根据参数uid查询用户数据// 检查查询结果是否为null// 是抛出UserNotFoundException// 检查查询结果中的isDelete是否为1// 是抛出UserNotFoundException// 创建当前时间对象// 调用userMapper的updateAvatarByUid()方法执行更新并获取返回值// 判断以上返回的受影响行数是否不为1// 是抛了UpdateException }2.changeAvatar(Integer uid, String username, String avatar)方法中代码的具体实现为。 Override public void changeAvatar(Integer uid, String username, String avatar) {// 调用userMapper的findByUid()方法根据参数uid查询用户数据User result userMapper.findByUid(uid);// 检查查询结果是否为nullif (result null) {// 是抛出UserNotFoundExceptionthrow new UserNotFoundException(用户数据不存在);}// 检查查询结果中的isDelete是否为1if (result.getIsDelete().equals(1)) {// 是抛出UserNotFoundExceptionthrow new UserNotFoundException(用户数据不存在);}// 创建当前时间对象Date now new Date();// 调用userMapper的updateAvatarByUid()方法执行更新并获取返回值Integer rows userMapper.updateAvatarByUid(uid, avatar, username, now);// 判断以上返回的受影响行数是否不为1if (rows ! 1) {// 是抛出UpdateExceptionthrow new UpdateException(更新用户数据时出现未知错误请联系系统管理员);} }3.在UserServiceTests类中进行单元测试。 Test public void changeAvatar() {try {Integer uid 20;String username 头像管理员;String avatar /upload/avatar.png;userService.changeAvatar(uid, username, avatar);System.out.println(OK.);} catch (ServiceException e) {System.out.println(e.getClass().getSimpleName());System.out.println(e.getMessage());} }3 用户-上传头像-控制器 3.1 处理异常 1.在处理上传文件的过程中用户可能会选择错误的文件上传此时就应该抛出对应的异常并进行处理。所以需要创建文件上传相关异常的基类即在com.cy.store.controller.ex包下创建FileUploadException类并继承自RuntimeException类。 package com.cy.store.service.ex;/** 文件上传相关异常的基类 */ public class FileUploadException extends RuntimeException {public FileUploadException() {super();}public FileUploadException(String message) {super(message);}public FileUploadException(String message, Throwable cause) {super(message, cause);}public FileUploadException(Throwable cause) {super(cause);}protected FileUploadException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);} }2.在处理上传的文件过程中经分析可能会产生以下异常。这些异常类都需要继承自FileUploadException类。 // 上传的文件为空 cn.tedu.store.controller.ex.FileEmptyException // 上传的文件大小超出了限制值 cn.tedu.store.controller.ex.FileSizeException // 上传的文件类型超出了限制 cn.tedu.store.controller.ex.FileTypeException // 上传的文件状态异常 cn.tedu.store.controller.ex.FileStateException // 上传文件时读写异常 cn.tedu.store.controller.ex.FileUploadIOException3.创建FileEmptyException异常类并继承FileUploadException类。 package com.cy.store.service.ex;/** 上传的文件为空的异常例如没有选择上传的文件就提交了表单或选择的文件是0字节的空文件 */ public class FileEmptyException extends FileUploadException {// Override Methods... }4.创建FileSizeException异常类并继承FileUploadException类。 package com.cy.store.service.ex;/** 上传的文件的大小超出了限制值 */ public class FileSizeException extends FileUploadException {// Override Methods... }5.创建FileTypeException异常类并继承FileUploadException类。 package com.cy.store.service.ex;/** 上传的文件类型超出了限制 */ public class FileTypeException extends FileUploadException {// Override Methods... }6.创建FileStateException异常类并继承FileUploadException类。 package com.cy.store.service.ex;/** 上传的文件状态异常 */ public class FileStateException extends FileUploadException {// Override Methods... }7.创建FileUploadIOException异常类并继承FileUploadException类。 package com.cy.store.service.ex;/** 上传文件时读写异常 */ public class FileUploadIOException extends FileUploadException {// Override Methods... }8.然后在BaseController的handleException()的ExceptionHandler注解中添加FileUploadException.class异常的处理最后在方法中处理这些异常。 ExceptionHandler({ServiceException.class, FileUploadException.class}) public JsonResultVoid handleException(Throwable e) {JsonResultVoid result new JsonResultVoid(e);if (e instanceof UsernameDuplicateException) {result.setState(4000);} else if (e instanceof UserNotFoundException) {result.setState(4001);} else if (e instanceof PasswordNotMatchException) {result.setState(4002);} else if (e instanceof InsertException) {result.setState(5000);} else if (e instanceof UpdateException) {result.setState(5001);} 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);}return result; }3.2 设计请求 设计用户提交的请求并设计响应的方式 请求路径/users/change_avatar 请求参数MultipartFile file, HttpSession session 请求类型POST 响应结果JsonResultString3.3 处理请求 1.在UserController类中添加处理请求的changeAvatar(RequestParam(“file”) MultipartFile file, HttpSession session)方法。 PostMapping(change_avatar) public JsonResultString changeAvatar(RequestParam(file) MultipartFile file, HttpSession session) {// 判断上传的文件是否为空// 是抛出异常// 判断上传的文件大小是否超出限制值// 是抛出异常// 判断上传的文件类型是否超出限制// 是抛出异常// 获取当前项目的绝对磁盘路径// 保存头像文件的文件夹// 保存的头像文件的文件名// 创建文件对象表示保存的头像文件// 执行保存头像文件// 如果产生异常则抛出// 头像路径// 从Session中获取uid和username// 将头像写入到数据库中// 返回成功和头像路径return null; }2.changeAvatar(RequestParam(“file”) MultipartFile file, HttpSession session)方法中具体代码实现为。 /** 头像文件大小的上限值(10MB) */ public static final int AVATAR_MAX_SIZE 10 * 1024 * 1024; /** 允许上传的头像的文件类型 */ public static final ListString AVATAR_TYPES new ArrayListString();/** 初始化允许上传的头像的文件类型 */ static {AVATAR_TYPES.add(image/jpeg);AVATAR_TYPES.add(image/png);AVATAR_TYPES.add(image/bmp);AVATAR_TYPES.add(image/gif); }PostMapping(change_avatar) public JsonResultString changeAvatar(RequestParam(file) MultipartFile file, HttpSession session) {// 判断上传的文件是否为空if (file.isEmpty()) {// 是抛出异常throw new FileEmptyException(上传的头像文件不允许为空);}// 判断上传的文件大小是否超出限制值if (file.getSize() AVATAR_MAX_SIZE) { // getSize()返回文件的大小以字节为单位// 是抛出异常throw new FileSizeException(不允许上传超过 (AVATAR_MAX_SIZE / 1024) KB的头像文件);}// 判断上传的文件类型是否超出限制String contentType file.getContentType();// public boolean list.contains(Object o)当前列表若包含某元素返回结果为true若不包含该元素返回结果为false。if (!AVATAR_TYPES.contains(contentType)) {// 是抛出异常throw new FileTypeException(不支持使用该类型的文件作为头像允许的文件类型\n AVATAR_TYPES);}// 获取当前项目的绝对磁盘路径String parent session.getServletContext().getRealPath(upload);// 保存头像文件的文件夹File dir new File(parent);if (!dir.exists()) {dir.mkdirs();}// 保存的头像文件的文件名String suffix ;String originalFilename file.getOriginalFilename();int beginIndex originalFilename.lastIndexOf(.);if (beginIndex 0) {suffix originalFilename.substring(beginIndex);}String filename UUID.randomUUID().toString() suffix;// 创建文件对象表示保存的头像文件File dest new File(dir, filename);// 执行保存头像文件try {file.transferTo(dest);} catch (IllegalStateException e) {// 抛出异常throw new FileStateException(文件状态异常可能文件已被移动或删除);} catch (IOException e) {// 抛出异常throw new FileUploadIOException(上传文件时读写错误请稍后重尝试);}// 头像路径String avatar /upload/ filename;// 从Session中获取uid和usernameInteger uid getUidFromSession(session);String username getUsernameFromSession(session);// 将头像写入到数据库中userService.changeAvatar(uid, username, avatar);// 返回成功头像路径return new JsonResultString(OK, avatar); }4 用户-上传头像-前端页面 1.然后在upload.html页面中配置用户上传头像的form表单。 2.完成后启动项目打开浏览器先登录再访问http://localhost:8080/web/upload.html进行测试。 5 用户-上传头像-设置上传文件大小 1.SpringBoot中默认MultipartResolver的最大文件大小值为1M。如果上传的文件的大小超过1M会抛FileSizeLimitExceededException异常。 2.如果需要调整上传的限制值直接在启动类中添加getMultipartConfigElement()方法并且在启动类之前添加Configuration注解。 package com.cy.store; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.MultipartConfigFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.unit.DataSize; import org.springframework.util.unit.DataUnit; import javax.servlet.MultipartConfigElement;Configuration SpringBootApplication MapperScan(com.cy.store.mapper) public class StoreApplication {public static void main(String[] args) {SpringApplication.run(StoreApplication.class, args);}Beanpublic MultipartConfigElement getMultipartConfigElement() {MultipartConfigFactory factory new MultipartConfigFactory();// DataSize dataSize DataSize.ofMegabytes(10);// 设置文件最大10MDataUnit提供5中类型B,KB,MB,GB,TBfactory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES));factory.setMaxRequestSize(DataSize.of(10, DataUnit.MEGABYTES));// 设置总上传数据总大小10Mreturn factory.createMultipartConfig();} }3.除了以上编写方法配置上传的上限值以外还可以通过在application.properties或application.yml中添加配置来实现。 (1) 低版本1.X spring.http.multipart.max-file-size10MB spring.http.multipart.max-request-size10MB(2) 高版本2.X #方式1 spring.servlet.multipart.max-file-size10MB spring.servlet.multipart.max-request-size10MB #方式2 spring.servlet.multipart.maxFileSize10MB spring.servlet.multipart.maxRequestSize10MB6 用户-上传头像-前端页面BUG解决 6.1 上传后显示头像 1.头像上传成功后显示上传的头像。在upload.html页面中是使用img标签来显示头像图片的。首先确定img标签是否添加有idimg-avatar属性便于后续访问该标签而img标签是通过src属性来决定显示哪张图片的所以修改src该属性的值即可设置需要显示的图片。修改表单添加idform-change-avatar属性。修改input标签添加idbtn-change-avatar和typebutton属性。 2.在upload.html页面中body标签内部的最后添加script标签用于编写JavaScript程序。 processData处理数据。默认情况下processData的值是true其代表以对象的形式上传的数据都会被转换为字符串的形式上传。而当上传文件的时候则不需要把其转换为字符串因此要改成false。contentType发送数据的格式。其代表的是前端发送数据的格式默认值application/x-www-form-urlencoded。代表的是ajax的 data是以字符串的形式传递使用这种传数据的格式无法传输复杂的数据比如多维数组、文件等。把contentType设置为false就会改掉之前默认的数据格式在上传文件时就不会报错。 script typetext/javascript$(#btn-change-avatar).click(function() {$.ajax({url: /users/change_avatar,type: POST,data: new FormData($(#form-change-avatar)[0]),dataType: JSON,processData: false, // processData处理数据contentType: false, // contentType发送数据的格式success: function(json) {if (json.state 200) {$(#img-avatar).attr(src, json.data);} else {alert(修改失败 json.message);}},error: function(xhr) {alert(您的登录信息已经过期请重新登录HTTP响应码 xhr.status);location.href login.html;}});}); /script3.完成后启动项目打开浏览器先登录再访问http://localhost:8080/web/upload.html进行测试。 6.2 登录后显示头像 1.首先检查登录成功后是否返回了头像的数据。访问http://localhost:8080/users/login?usernameadminpassword321测试。 2.用户名、用户Id、用户头像等数据属于常用数据在客户端的许多页面都可能需要使用如果每次都向服务器提交请求获取这些数据是非常不合适的。可以在用户登录成功后将这些数据存储在客户端本地后续在客户端中需要显示这些数据时直接从本地获取即可无需再向服务器请求这些数据。在客户端本地存取数据时可以使用Cookie技术。 3.设计思路当用户登录成功后将服务器返回的头像路径存储到本地的Cookie中在打开“上传头像”页面时从本地的Cookie中读取头像路径并显示即可。在登录login.html页面中当登录成功后将用户头像路径保存到Cookie中。 $(#btn-login).click(function() {$.ajax({url: /users/login,type: POST,data: $(#form-login).serialize(),dataType: json,success: function(json) {if (json.state 200) {alert(登录成功);$.cookie(avatar, json.data.avatar, {expires: 7});console.log(cookie中的avatar $.cookie(avatar));location.href index.html;} else {alert(登录失败 json.message);}}}); });语法$.cookie(名称,值,[option])。[option]参数说明 expires有限日期可以是一个整数或一个日期(单位天)。如果不设置这个值默认情况下浏览器关闭之后此Cookie就会失效。 path表示Cookie值保存的路径默认与创建页路径一致。 domin表示Cookie域名属性默认与创建页域名一样。要注意跨域的概念如果要主域名二级域名有效则要设置“.xxx.com”。 secrue布尔类型的值表示传输Cookie值时是否需要一个安全协议。 4.在upload.html页面中默认并没有引用jqueyr.cookie.js文件因此无法识别$.cookie()函数所以需要在upload.html页面head标签内添加jqueyr.cookie.js文件。 script src../bootstrap3/js/jquery.cookie.js typetext/javascript charsetutf-8/script5.在打开页面时自动读取显示用户图像。获取Cookie中头像的路径然后将获取到的头像路径设置给img标签的src属性以显示头像。在upload.html页面中的script标签的内部添加自动读取用户图像的jquery代码。 $(document).ready(function () {console.log(cookie中的avatar $.cookie(avatar));$(#img-avatar).attr(src, $.cookie(avatar)); });6.3 显示最新头像 以上代码表示“每次打开页面时读取Cookie中的头像并显示”如果此时重新上传用户头像而Cookie中所保存的头像还是之前上传的头像路径值无法显示最新的用户头像。所以当用户重新上传头像后还应把新头像的路径更新到Cookie中。 1.在upload.html页面中用户头像修改成功后并将新的用户头像路径保存到Cookie中。 $.cookie(avatar, json.data, {expires: 7});2.完成后启动项目打开浏览器先登录再访问http://localhost:8080/web/upload.html进行测试。 要在upload.html页面head标签内添加jqueyr.cookie.js文件。 script src../bootstrap3/js/jquery.cookie.js typetext/javascript charsetutf-8/script5.在打开页面时自动读取显示用户图像。获取Cookie中头像的路径然后将获取到的头像路径设置给img标签的src属性以显示头像。在upload.html页面中的script标签的内部添加自动读取用户图像的jquery代码。 $(document).ready(function () {console.log(cookie中的avatar $.cookie(avatar));$(#img-avatar).attr(src, $.cookie(avatar)); });6.3 显示最新头像 以上代码表示“每次打开页面时读取Cookie中的头像并显示”如果此时重新上传用户头像而Cookie中所保存的头像还是之前上传的头像路径值无法显示最新的用户头像。所以当用户重新上传头像后还应把新头像的路径更新到Cookie中。 1.在upload.html页面中用户头像修改成功后并将新的用户头像路径保存到Cookie中。 $.cookie(avatar, json.data, {expires: 7});新增收货地址 1 新增收货地址-创建数据表 1.使用use命令先选中store数据库。 USE store;2.在store数据库中创建t_address用户数据表。 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) ) ENGINEInnoDB DEFAULT CHARSETutf8;2 新增收货地址-创建实体类 创建com.cy.store.entity.Address新增收获地址的实体类继承自BaseEntity类在类中声明与数据表中对应的属性添加Getters and Setters方法基于唯一标识aid生成hashCode()和equals()方法。 package com.cy.store.entity;/** 收货地址数据的实体类 */ 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;// Generate: Getter and Setter、Generate hashCode() and equals()、toString() }3 新增收货地址-持久层 3.1 各功能的开发顺序 关于收货地址数据的管理涉及的功能有增加删除修改设为默认显示列表。这些功能的开发顺序为增加-显示列表-设为默认-删除-修改。 3.2 规划需要执行的SQL语句 增加收货地址的本质是插入新的收货地址数据需要执行的SQL语句大致是 INSERT INTO t_address (除了aid以外的字段列表) VALUES (匹配的值列表)后续在处理业务时还需要确定“即将增加的收货地址是不是默认收货地址”可以设定规则“用户的第1条收货地址是默认的以后添加的每一条都不是默认的”要应用该规则就必须知道“即将增加的收货地址是不是第1条”可以“根据用户id统计收货地址的数量”如果统计结果为0则即将增加的就是该用户的第1条收货地址如果统计结果不是0则该用户已经有若干条收货地址了即将增加的就一定不是第1条。关于统计的SQL语句大致是 SELECT count(*) FROM t_address WHERE uid?一般电商平台都会限制每个用户可以创建的收货地址的数量如“每个用户最多只允许创建20个收货地址”也可以通过以上查询来实现。 3.3 接口与抽象方法 创建com.cy.store.mapper.AddressMapper接口并在接口中添加抽象方法。 package com.cy.store.mapper; import com.cy.store.entity.Address;/** 处理收货地址数据的持久层接口 */ public interface AddressMapper {/*** 插入收货地址数据* param address 收货地址数据* return 受影响的行数*/Integer insert(Address address);/*** 统计某用户的收货地址数据的数量* param uid 用户的id* return 该用户的收货地址数据的数量*/Integer countByUid(Integer uid); }3.4 配置SQL映射 1.在src/main/resources/mapper文件夹下复制粘贴得到AddressMapper.xml映射文件修改根节点mapper的namespace属性的值为com.cy.store.mapper.AddressMapper并在根节点中配置pojo类属性与数据库中表的字段映射。 ?xml version1.0 encodingUTF-8 ? !DOCTYPE mapperPUBLIC -//mybatis.org//DTD Mapper 3.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.cy.store.mapper.AddressMapperresultMap idAddressEntityMap typecom.cy.store.entity.Addressid columnaid propertyaid/result columnprovince_code propertyprovinceCode/result columnprovince_name propertyprovinceName/result columncity_code propertycityCode/result columncity_name propertycityName/result columnarea_code propertyareaCode/result columnarea_name propertyareaName/result columnis_default propertyisDefault/result columncreated_user propertycreatedUser/result columncreated_time propertycreatedTime/result columnmodified_user propertymodifiedUser/result columnmodified_time propertymodifiedTime//resultMap /mapper2.在AddressMapper.xml映射文件的根节点中配置以上两个抽象方法的映射。 !-- 插入收货地址数据Integer insert(Address address) -- insert idinsert useGeneratedKeystrue keyPropertyaidINSERT 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!-- 统计某用户的收货地址数据的数量Integer countByUid(Integer uid) -- select idcountByUid resultTypejava.lang.IntegerSELECTCOUNT(*)FROMt_addressWHEREuid#{uid} /select3.在src/test/java下创建com.cy.store.mapper.AddressMapperTests测试类在类定义之前添加测试的两个注解在类中编写并执行以上两个抽象方法的测试。 package com.cy.store.mapper; import com.cy.store.entity.Address; 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;RunWith(SpringRunner.class) SpringBootTest public class AddressMapperTests {Autowiredprivate AddressMapper addressMapper;Testpublic void insert() {Address address new Address();address.setUid(18);address.setName(admin);address.setPhone(17858802974);address.setAddress(雁塔区小寨赛格);Integer rows addressMapper.insert(address);System.out.println(rows rows);}Testpublic void countByUid() {Integer uid 18;Integer count addressMapper.countByUid(uid);System.out.println(count count);} }4 增收货地址-业务层 4.1 规划异常 1.无论用户将要增加的收货地址是不是默认收货地址都需正常增加。即通过countByUid()方法统计的结果不管是不是0都不能代表是错误的操作。 2.在执行插入收货地址数据之前需判断countByUid()方法返回值是否超出上限值如果超出上限值则抛AddressCountLimitException异常。 3.在执行插入数据时还可能抛出InsertException异常此异常无需再次创建。 4.创建com.cy.store.service.ex.AddressCountLimitException类后需继承自ServiceException类。 package com.cy.store.service.ex;/** 收货地址数量达到上限的异常 */ public class AddressCountLimitException extends ServiceException {// Override Methods... }4.2 接口与抽象方法 创建com.cy.store.service.IAddressService业务层接口并添加抽象方法。 package com.cy.store.service; import com.cy.store.entity.Address;/** 处理收货地址数据的业务层接口 */ public interface IAddressService {/*** 创建新的收货地址* param uid 当前登录的用户的id* param username 当前登录的用户名* param address 用户提交的收货地址数据*/void addNewAddress(Integer uid, String username, Address address); }4.3 实现抽象方法 1.创建com.cy.store.service.impl.AddressServiceImpl业务层实现类在类定义之前添加Service注解并实现IAddressService接口最后在类中添加持久层对象并使用Autowired注解修饰。 package com.cy.store.service.impl; import com.cy.store.entity.Address; import com.cy.store.mapper.AddressMapper; import com.cy.store.service.IAddressService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;Service public class AddressServiceImpl implements IAddressService {Autowiredprivate AddressMapper addressMapper;Overridepublic void addNewAddress(Integer uid, String username, Address address) {// TODO} }2.分析重写的addNewAddress(Integer uid, String username, Address address)抽象方法中的业务逻辑。 Override public void addNewAddress(Integer uid, String username, Address address) {// 根据参数uid调用addressMapper的countByUid()方法统计当前用户的收货地址数据的数量// 判断数量是否达到上限值// 是抛出AddressCountLimitException// 补全数据将参数uid封装到参数address中// 补全数据根据以上统计的数量得到正确的isDefault值(是否默认0-不默认1-默认)并封装// 补全数据4项日志// 调用addressMapper的insert()方法插入收货地址数据并获取返回的受影响行数// 判断受影响行数是否不为1// 是抛出InsertException }3.addNewAddress(Integer uid, String username, Address address)方法的具体代码实现。 package com.cy.store.service.impl; import com.cy.store.entity.Address; import com.cy.store.mapper.AddressMapper; import com.cy.store.service.IAddressService; import com.cy.store.service.ex.AddressCountLimitException; import com.cy.store.service.ex.InsertException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.Date;Service public class AddressServiceImpl implements IAddressService {Autowiredprivate AddressMapper addressMapper;Value(${user.address.max-count})private int maxCount;Overridepublic void addNewAddress(Integer uid, String username, Address address) {// 根据参数uid调用addressMapper的countByUid(Integer uid)方法统计当前用户的收货地址数据的数量Integer count addressMapper.countByUid(uid);// 判断数量是否达到上限值if (count maxCount) {// 是抛出AddressCountLimitExceptionthrow new AddressCountLimitException(收货地址数量已经达到上限( maxCount ));}// 补全数据将参数uid封装到参数address中address.setUid(uid);// 补全数据根据以上统计的数量得到正确的isDefault值(是否默认0-不默认1-默认)并封装Integer isDefault count 0 ? 1 : 0;address.setIsDefault(IsDefault);// 补全数据4项日志Date now new Date();address.setCreatedUser(username);address.setCreatedTime(now);address.setModifiedUser(username);address.setModifiedTime(now);// 调用addressMapper的insert(Address address)方法插入收货地址数据并获取返回的受影响行数Integer rows addressMapper.insert(address);// 判断受影响行数是否不为1if (rows ! 1) {// 是抛出InsertExceptionthrow new InsertException(插入收货地址数据时出现未知错误请联系系统管理员);}} }4.在application.properties文件中添加收货地址数据上限值的配置。 user.address.max-count205.在src/test/java下创建com.cy.store.service.AddressServiceTests测试类在测试类中测试以上方法。 package com.cy.store.service; import com.cy.store.entity.Address; import com.cy.store.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;RunWith(SpringRunner.class) SpringBootTest public class AddressServiceTests {Autowiredprivate IAddressService addressService;Testpublic void addNewAddress() {try {Integer uid 20;String username 管理员;Address address new Address();address.setName(张三);address.setPhone(17858805555);address.setAddress(雁塔区小寨华旗);addressService.addNewAddress(uid, username, address);System.out.println(OK.);} catch (ServiceException e) {System.out.println(e.getClass().getSimpleName());System.out.println(e.getMessage());}} }5 新增收货地址-控制器 5.1 处理异常 在控制器层新增收货地址时如果收货地址已经达到上限值则抛出AddressCountLimitException异常并在BaseController类中添加处理AddressCountLimitException的异常。 // ... else if (e instanceof AddressCountLimitException) {result.setState(4003); } // ...5.2 设计请求 设计用户提交的请求并设计响应的方式。 请求路径/addresses/add_new_address 请求参数Address address, HttpSession session 请求类型POST 响应结果JsonResultVoid5.3 处理请求 1.创建com.cy.store.controller.AddressController控制器类继承自BaseController类在类的声明添加RequestMapping(“addresses”)和RestController注解在类中声明业务层对象并添加Autowired注解修饰。 package com.cy.store.controller; import com.cy.store.service.IAddressService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;RestController RequestMapping(addresses) public class AddressController extends BaseController {Autowiredprivate IAddressService addressService; }2.然后在AddressController类中添加处理请求的addNewAddress(Address address, HttpSession session)方法。 RequestMapping(add_new_address) public JsonResultVoid addNewAddress(Address address, HttpSession session) {// 从Session中获取uid和usernameInteger uid getUidFromSession(session);String username getUsernameFromSession(session);// 调用业务对象的方法执行业务addressService.addNewAddress(uid, username, address);// 响应成功return new JsonResultVoid(OK); }3.完成后启动项目打开浏览器先登录再访问http://localhost:8080/addresses/add_new_address进行测试。 6 新增收货地址-前端页面 1.在addAddress.html页面中配置新增收货地址表单的属性。给form表单添加idform-add-new-address属性、请输入收货人姓名添加namename属性、请输入邮政编码添加namezip属性、输入详细的收货地址小区名称、门牌号等添加nameaddress属性、请输入手机号码添加namephone属性、请输入固定电话号码添加nametel属性、请输入地址类型如家、公司或者学校添加nametag属性、保存按钮添加idbtn-add-new-address属性。以上属性如果已经添加无需重复添加。 2.在addAddress.html页面中body标签内部的最后添加script标签用于编写JavaScript程序。 script typetext/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(新增收货地址成功);} else {alert(新增收货地址失败 json.message);}},error: function(xhr) {alert(您的登录信息已经过期请重新登录HTTP响应码 xhr.status);location.href login.html;}});}); /script3.完成后启动项目打开浏览器先登录再访问http://localhost:8080/web/addAddress.html页面。 获取省/市/区的列表 1 获取省/市/区的列表-数据库 1.使用该数据库 USE store;2.向数据库中导入省/市/区数据t_dict_district.sql文件执行以下指令 mysql source C:/Users/yuanxin/t_dict_district.sql3.创建省/市/区数据的com.cy.store.entity实体类在类中声明与数据表中对应的属性添加Getters and Setters方法基于唯一标识id生成equals()方法及hashCode()和toString()方法。 package com.cy.store.entity; import java.io.Serializable;/** 省/市/区数据的实体类 */ public class District implements Serializable {private Integer id;private String parent;private String code;private String name;// Generate: Getter and Setter、Generate hashCode() and equals()、toString() }2 获取省/市/区的列表-持久层 2.1 规划需要执行的SQL语句 获取全国所有省/某省所有市/某市所有区的查询SQL语句大致是 select * from t_dict_district where parent? order by code ASC;2.2 接口与抽象方法 创建com.cy.store.mapper.DistrictMapper接口添加抽象方法。 package com.cy.store.mapper; import com.cy.store.entity.District; import java.util.List;/** 处理省/市/区数据的持久层接口 */ public interface DistrictMapper {/*** 获取全国所有省/某省所有市/某市所有区* param parent 父级代号当获取某市所有区时使用市的代号当获取省所有市时使用省的代号当获取全国所有省时使用86作为父级代号* return 全国所有省/某省所有市/某市所有区的列表*/ListDistrict findByParent(String parent); }2.3 配置SQL映射 1.在src/main/resources/mapper中复制得到DistrictMapper.xml修改根节点的namespace属性的值为以上接口文件并配置以上抽象方法的映射。 ?xml version1.0 encodingUTF-8 ? !DOCTYPE mapperPUBLIC -//mybatis.org//DTD Mapper 3.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.cy.store.mapper.DistrictMapper!-- 获取全国所有省/某省所有市/某市所有区ListDistrict findByParent(String parent) --select idfindByParent resultTypecom.cy.store.entity.DistrictSELECT*FROMt_dict_districtWHEREparent#{parent}ORDER BYcode ASC/select /mapper2.在src/test/java下创建com.cy.store.mapper.DistrictMapperTests测试类编写并执行以上抽象方法的测试。 package com.cy.store.mapper; import com.cy.store.entity.District; 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; import java.util.List;RunWith(SpringRunner.class) SpringBootTest public class DistrictMapperTests {Autowiredprivate DistrictMapper districtMapper;Testpublic void findByParent() {String parent 110100;ListDistrict list districtMapper.findByParent(parent);System.out.println(count list.size());for (District district : list) {System.out.println(district);}} }3 获取省/市/区的列表-业务层 3.1 规划异常 说明无异常。 3.2 接口与抽象方法 创建com.cy.store.service.IDistrictService接口并添加抽象方法。 package com.cy.store.service; import com.cy.store.entity.District; import java.util.List;/** 处理省/市/区数据的业务层接口 */ public interface IDistrictService {/*** 获取全国所有省/某省所有市/某市所有区* param parent 父级代号当获取某市所有区时使用市的代号当获取某省所有市时使用省的代号当获取全国所有省时使用86作为父级代号* return 全国所有省/某省所有市/某市所有区的列表*/ListDistrict getByParent(String parent); }3.3 实现抽象方法 1.创建com.cy.store.service.impl.DistrictServiceImpl类实现IDistrictService接口在类之前添加Service注解以及在类中添加持久层对象并使用Autowired修饰。 package com.cy.store.service.impl; import com.cy.store.entity.District; import com.cy.store.mapper.DistrictMapper; import com.cy.store.service.IDistrictService; import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service; import java.util.List;/** 处理省/市/区数据的业务层实现类 */ Service public class DistrictServiceImpl implements IDistrictService {Autowiredprivate DistrictMapper districtMapper;Overridepublic ListDistrict getByParent(String parent) {return null;} }2.在DistrictServiceImpl实现类中实现getByParent(String parent)方法的具体代码。 Override public ListDistrict getByParent(String parent) {ListDistrict list districtMapper.findByParent(parent);for (District district : list) {district.setId(null);district.setParent(null);}return list; }3.在src/test/java下创建com.cy.store.service.DistrictServiceTests测试类编写并执行单元测试。 package com.cy.store.service; import com.cy.store.entity.District; import com.cy.store.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; import java.util.List;RunWith(SpringRunner.class) SpringBootTest public class DistrictServiceTests {Autowiredprivate IDistrictService districtService;Testpublic void getByParent() {try {String parent 86;ListDistrict list districtService.getByParent(parent);System.out.println(count list.size());for (District item : list) {System.out.println(item);}} catch (ServiceException e) {System.out.println(e.getClass().getSimpleName());System.out.println(e.getMessage());}} }4 获取省/市/区的列表-控制器 4.1 处理异常 说明无异常。 4.2 设计请求 设计用户提交的请求并设计响应的方式。 请求路径/districts/ 请求参数String parent 请求类型GET 响应结果JsonResultListDistrict 是否拦截否需要在拦截器的配置中添加白名单4.3 处理请求 1.创建com.cy.store.controller.DistrictController控制器类继承自BaseController类在类之前添加RequestMapping(“districts”)和RestController注解并在类中添加业务层对象对其使用Autowired注解修饰。 package com.cy.store.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import cn.tedu.store.entity.District; import cn.tedu.store.service.IDistrictService; import cn.tedu.store.util.JsonResult;RequestMapping(districts) RestController public class DistrictController extends BaseController {Autowiredprivate IDistrictService districtService;}2.在类中添加处理请求的方法getByParent(String parent)及方法的实现。 GetMapping是一个组合注解等价于RequestMapping(method{RequestMethod.GET})它将HTTP的GET请求映射到特定的处理方法上。“/”表示方法将处理所有传入的URI请求。简化代码。 GetMapping({, /}) public JsonResultListDistrict getByParent(String parent) {ListDistrict data districtService.getByParent(parent);return new JsonResult(OK, data); }3.在拦截器LoginInterceptorConfigurer类的addInterceptors(InterceptorRegistry registry)方法中将“districts”请求添加为白名单。如果已经添加无需重复添加。 patterns.add(/districts/**);4.完成后启动项目打开浏览器不需要登录直接访问http://localhost:8080/districts?parent86进行测试。 5 获取省/市/区的列表-前端页面 1.在addAddress.html页面中的head标签内导入的distpicker.data.js和distpicker.js文件注释掉。 JQuery实现中国省市区地址三级联动插件Distpicker。 !-- script typetext/javascript src../js/distpicker.data.js/script script typetext/javascript src../js/distpicker.js/script --2.在新增收货地址表单中给选择省控件添加nameprovinceCode和idprovince-list属性给选择市添加namecityCode和idcity-list属性给选择区控件添加nameareaCode和idarea-list属性。以上属性如果已经添加无需重复添加。 3.在addAddress.html页面中body标签内的script标签中添加获取省/市/区列表的代码。 script typetext/javascriptlet defaultOption option value0----- 请选择 -----/option;$(document).ready(function() {showProvinceList();$(#city-list).append(defaultOption);$(#area-list).append(defaultOption);});$(#province-list).change(function() {showCityList();});$(#city-list).change(function() {showAreaList();});function showProvinceList() {$(#province-list).append(defaultOption);$.ajax({url: /districts,type: GET,data: parent86,dataType: JSON,success: function(json) {if (json.state 200) {let list json.data;console.log(count list.length);for (let i 0; i list.length; i) {console.log(list[i].name);let option option value list[i].code list[i].name /option;$(#province-list).append(option);}}}});}function showCityList() {let parent $(#province-list).val();$(#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;console.log(count list.length);for (let i 0; i list.length; i) {console.log(list[i].name);let option option value list[i].code list[i].name /option;$(#city-list).append(option);}}}});}function showAreaList() {let parent $(#city-list).val();$(#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;console.log(count list.length);for (let i 0; i list.length; i) {console.log(list[i].name);let option option value list[i].code list[i].name /option;$(#area-list).append(option);}}}});} /scriptJQuery事件-change()方法 1.定义和用法 1当元素的值发生改变时会发生change事件。 2该事件仅适用于文本域(textfield)以及textarea和select元素。 3change()函数触发change事件或规定当发生change事件时运行的函数。 当用于select元素时change事件会在选择某个选项时发生。当用于textfield或textarea时该事件会在元素失去焦点时发生。 2.触发change事件 触发被选元素的change事件。语法$(selector).change() 3.将函数绑定到change事件 规定当被选元素的 change 事件发生时运行的函数。语法$(selector).change(function) 4.完成后启动项目打开浏览器先登录再访问http://localhost:8080/web/addAddress.html页面。 5.说明如果输入的邮政编码位数大于6位数字则会抛MysqlDataTruncation异常。 获取省/市/区的名称 此功能模块主要实现根据省/市/区的行政代号获取省/市/区的名称。 1 获取省/市/区的名称-持久层 1.1 规划需要执行的SQL语句 根据省/市/区的行政代号获取省/市/区的名称需要执行的SQL语句大致是 select name from t_dict_district where code?1.2 接口与抽象方法 在DistrictMapper接口中添加根据省/市/区的行政代号获取省/市/区的名称findNameByCode(String code)抽象方法。 /*** 根据省/市/区的行政代号获取省/市/区的名称* param code 省/市/区的行政代号* return 匹配的省/市/区的名称如果没有匹配的数据则返回null*/ String findNameByCode(String code);1.3 配置SQL映射 1.在DistrictMapper.xml文件中配置映射。 !-- 根据省/市/区的行政代号获取省/市/区的名称String findNameByCode(String code) -- select idfindNameByCode resultTypejava.lang.StringSELECTnameFROMt_dict_districtWHEREcode#{code} /select2.然后在DistrictMapperTests测试类中编写并执行测试方法。 Test public void findNameByCode() {String code 540000;String name districtMapper.findNameByCode(code);System.out.println(name); }2 获取省/市/区的名称-业务层 2.1 规划异常 说明无异常。 2.2 接口与抽象方法 在业务层IDistrictService接口中添加getNameByCode(String code)抽象方法。 /*** 根据省/市/区的行政代号获取省/市/区的名称* param code 省/市/区的行政代号* return 匹配的省/市/区的名称如果没有匹配的数据则返回null*/ String getNameByCode(String code);2.3 实现抽象方法 1.在业务层DistrictServiceImpl类中重写getNameByCode(String code)方法。 Override public String getNameByCode(String code) {return districtMapper.findNameByCode(code); }2.然后在DistrictServiceTests测试类中编写并执行测试方法。 Test public void getNameByCode() {try {String code 430000;String result districtService.getNameByCode(code);System.out.println(result);} catch (ServiceException e) {System.out.println(e.getClass().getSimpleName());System.out.println(e.getMessage());} }3 新增收货地址-业务层优化 1.在AddressServiceImpl类中声明处理省/市/区数据的业务层对象。 Autowired private IDistrictService districtService;2.在addNewAddress(Integer uid, String username, Address address)方法中补全省/市/区数据。 // 补全数据省、市、区的名称 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.完成后启动项目打开浏览器先登录再访问http://localhost:8080/web/addAddress.html页面。输入收货人相关的信息并保存。 2.在后台数据库中检查数据是否被正常的插入到t_address表中。 收货地址列表 1 收货地址列表显示-持久层 1.1 规划需要执行的SQL语句 显示当前登录用户的收货地址列表的SQL语句大致是 select * from t_address where uid? order by is_default desc, created_time desc;1.2 接口与抽象方法 在AddressMapper接口中添加findByUid(Integer uid)抽象方法。 /*** 查询某用户的收货地址列表数据* param uid 收货地址归属的用户id* return 该用户的收货地址列表数据*/ ListAddress findByUid(Integer uid);1.3 配置SQL映射 1.在AddressMapper.xml文件中配置findByUid(Integer uid)方法的映射。 !-- resultMap idAddressEntityMap typecn.tedu.store.entity.Addressid columnaid propertyaid/result columnprovince_code propertyprovinceCode/result columnprovince_name propertyprovinceName/result columncity_code propertycityCode/result columncity_name propertycityName/result columnarea_code propertyareaCode/result columnarea_name propertyareaName/result columnis_default propertyisDefault/result columncreated_user propertycreatedUser/result columncreated_time propertycreatedTime/result columnmodified_user propertymodifiedUser/result columnmodified_time propertymodifiedTime/ /resultMap --!-- 查询某用户的收货地址列表数据ListAddress findByUid(Integer uid) -- select idfindByUid resultMapAddressEntityMapSELECT*FROMt_addressWHEREuid#{uid}ORDER BYis_default DESC, created_time DESC /select2.在AddressMapperTests测试类中添加findByUid()测试方法。 Test public void findByUid() {Integer uid 26;ListAddress list addressMapper.findByUid(uid);System.out.println(count list.size());for (Address item : list) {System.out.println(item);} }2 收货地址列表显示-业务层 2.1 规划异常 说明无异常。 2.2 接口与抽象方法 在IAddressService接口中添加getByUid(Integer uid)抽象方法。 /*** 查询某用户的收货地址列表数据* param uid 收货地址归属的用户id* return 该用户的收货地址列表数据*/ ListAddress getByUid(Integer uid);2.3 实现抽象方法 1.在AddressServiceImpl类中实现getByUid(Integer uid)抽象方法。 Override public ListAddress getByUid(Integer uid) {ListAddress list addressMapper.findByUid(uid);for (Address address : list) {address.setUid(null);address.setProvinceCode(null);address.setCityCode(null);address.setAreaCode(null);address.setCreatedUser(null);address.setCreatedTime(null);address.setModifiedUser(null);address.setModifiedTime(null);}return list; }2.在AddressServiceTests测试类中添加getByUid()测试方法。 Test public void getByUid() {Integer uid 26;ListAddress list addressService.getByUid(uid);System.out.println(count list.size());for (Address item : list) {System.out.println(item);} }3 收货地址列表显示-控制器 3.1 处理异常 说明无异常。 3.2 设计请求 设计用户提交的请求并设计响应的方式。 请求路径/addresses 请求参数HttpSession session 请求类型GET 响应结果JsonResultListAddress3.3 处理请求 1.在AddressController类中添加处理请求的getByUid(HttpSession session)方法。 GetMapping({, /}) public JsonResultListAddress getByUid(HttpSession session) {Integer uid getUidFromSession(session);ListAddress data addressService.getByUid(uid);return new JsonResult(OK, data); }4 收货地址列表显示-前端页面 1.在address.html页面中body标签内部的最后添加展示用户收货地址列表数据的JavaScript代码。 script typetext/javascript $(document).ready(function () {showAddressList(); });function showAddressList() {$(#address-list).empty();$.ajax({url: /addresses,type: GET,dataType: JSON,success: function (json) {let list json.data;for (let i 0; i list.length; i) {console.log(list[i].name);let address tr td#{tag}/td td#{name}/td td#{province}#{city}#{area}#{address}/td td#{phone}/td tda classbtn btn-xs btn-infospan classfa fa-edit/span 修改/a/td tda classbtn btn-xs add-del btn-infospan classfa fa-trash-o/span 删除/a/td tda classbtn btn-xs add-def btn-default设为默认/a/td /tr;address address.replace(/#{aid}/g, list[i].aid);address address.replace(/#{tag}/g, list[i].tag);address address.replace(#{name}, list[i].name);address address.replace(#{province}, list[i].provinceName);address address.replace(#{city}, list[i].cityName);address address.replace(#{area}, list[i].areaName);address address.replace(#{address}, list[i].address);address address.replace(#{phone}, list[i].phone);$(#address-list).append(address);}$(.add-def:eq(0)).hide();}}); } /script2.完成后启动项目打开浏览器先登录再访问http://localhost:8080/web/address.html页面。 默认收货地址 1 默认收货地址-持久层 1.1 规划需要执行的SQL语句 1.将某用户的所有收货地址设置为非默认地址是否默认0-不默认1-默认。 update t_address set is_default0 where uid?2.将某用户指定的收货地址设置为默认地址。 update t_address set is_default1, modified_user?, modified_time? where aid?3.检查该收货地址是否存在并检查数据归属是否正确。可根据收货地址aid值查询收货地址详情数据。 select * from t_address where aid?1.2 接口与抽象方法 在AddressMapper接口中声明三个抽象方法。 /*** 将某用户的所有收货地址设置为非默认地址* param uid 收货地址归属的用户id* return 受影响的行数*/ Integer updateNonDefaultByUid(Integer uid);/*** 将指定的收货地址设置为默认地址* param aid 收货地址id* param modifiedUser 修改执行人* param modifiedTime 修改时间* return 受影响的行数*/ Integer updateDefaultByAid(Param(aid) Integer aid,Param(modifiedUser) String modifiedUser,Param(modifiedTime) Date modifiedTime);/*** 根据收货地址aid值查询收货地址详情* param aid 收货地址id* return 匹配的收货地址详情如果没有匹配的数据则返回null*/ Address findByAid(Integer aid);1.3 配置SQL映射 1.在AddressMapper.xml映射文件配置以上三个抽象方法的映射。 !-- 将某用户的所有收货地址设置为非默认地址Integer updateNonDefaultByUid(Integer uid) -- update idupdateNonDefaultByUidUPDATEt_addressSETis_default0WHEREuid#{uid} /update!-- 将指定的收货地址设置为默认地址Integer updateDefaultByAid(Param(aid) Integer aid,Param(modifiedUser) String modifiedUser,Param(modifiedTime) Date modifiedTime) -- update idupdateDefaultByAidUPDATEt_addressSETis_default1,modified_user#{modifiedUser},modified_time#{modifiedTime}WHEREaid#{aid} /update!-- 根据收货地址aid值查询收货地址详情Address findByAid(Integer aid) -- select idfindByAid resultMapAddressEntityMapSELECT*FROMt_addressWHEREaid#{aid} /select2.在AddressMapperTests类中编写并执行以上三个抽象方法的测试。 Test public void updateNonDefaultByUid() {Integer uid 26;Integer rows addressMapper.updateNonDefaultByUid(uid);System.out.println(rows rows); }Test public void updateDefaultByAid() {Integer aid 11;String modifiedUser 管理员;Date modifiedTime new Date();Integer rows addressMapper.updateDefaultByAid(aid, modifiedUser, modifiedTime);System.out.println(rows rows); }Test public void findByAid() {Integer aid 11;Address result addressMapper.findByAid(aid);System.out.println(result); }2 默认收货地址-业务层 2.1 规划异常 1.在执行设置默认收货地址之前需要先检查该收货地址数据是否存在如果不存在则抛出AddressNotFoundException异常。 2.然后还需要检查数据归属是否正确也就是不可以操作他人的数据如果该数据中记录的uid与当前登录的用户的uid不一致则抛出AccessDeniedException异常。 3.检查通过后先全部设置为非默认然后将指定的收货地址设置为默认这两种操作都是更新数据的操作则可能抛出UpdateException异常。 4.在com.cy.store.service.ex包下创建AddressNotFoundException和AccessDeniedException异常类。 package com.cy.store.service.ex;/** 收货地址数据不存在的异常 */ public class AddressNotFoundException extends ServiceException {// Override Methods... }package com.cy.store.service.ex;/** 非法访问的异常 */ public class AccessDeniedException extends ServiceException {// Override Methods... }2.2 接口与抽象方法 在IAddressService接口中添加setDefault(Integer aid, Integer uid, String username)抽象方法。 /*** 设置默认收货地址* param aid 收货地址id* param uid 归属的用户id* param username 当前登录的用户名*/ void setDefault(Integer aid, Integer uid, String username);2.3 实现抽象方法 1.在AddressServiceImpl类中重写setDefault(Integer aid, Integer uid, String username)方法。该方法需要添加Transactional注解。 事务基于Spring JDBC的事务Transaction处理使用事务可以保证一系列的增删改操作要么全部执行成功要么全部执行失败。Transactional注解可以用来修饰类也可以用来修饰方法。如果添加在业务类之前则该业务类中的方法均以事务的机制运行但是一般并不推荐这样处理。 Transactional Override public void setDefault(Integer aid, Integer uid, String username) {// 根据参数aid调用addressMapper中的findByAid()查询收货地址数据// 判断查询结果是否为null// 是抛出AddressNotFoundException// 判断查询结果中的uid与参数uid是否不一致(使用equals()判断)// 是抛出AccessDeniedException非法访问// 调用addressMapepr的updateNonDefaultByUid()将该用户的所有收货地址全部设置为非默认并获取返回的受影响的行数// 判断受影响的行数是否小于1(不大于0)// 是抛出UpdateException// 调用addressMapepr的updateDefaultByAid()将指定aid的收货地址设置为默认并获取返回的受影响的行数// 判断受影响的行数是否不为1// 是抛出UpdateException }2.setDefault(Integer aid, Integer uid, String username)方法的具体代码实现。 Transactional Override public void setDefault(Integer aid, Integer uid, String username) {// 根据参数aid调用addressMapper中的findByAid()查询收货地址数据Address result addressMapper.findByAid(aid);// 判断查询结果是否为nullif (result null) {// 是抛出AddressNotFoundExceptionthrow new AddressNotFoundException(尝试访问的收货地址数据不存在);}// 判断查询结果中的uid与参数uid是否不一致(使用equals()判断)if (!result.getUid().equals(uid)) {// 是抛出AccessDeniedExceptionthrow new AccessDeniedException(非法访问的异常);}// 调用addressMapper的updateNonDefaultByUid()将该用户的所有收货地址全部设置为非默认并获取返回受影响的行数Integer rows addressMapper.updateNonDefaultByUid(uid);// 判断受影响的行数是否小于1(不大于0)if (rows 1) {// 是抛出UpdateExceptionthrow new UpdateException(设置默认收货地址时出现未知错误[1]);}// 调用addressMapper的updateDefaultByAid()将指定aid的收货地址设置为默认并获取返回的受影响的行数rows addressMapper.updateDefaultByAid(aid, username, new Date());// 判断受影响的行数是否不为1if (rows ! 1) {// 是抛出UpdateExceptionthrow new UpdateException(设置默认收货地址时出现未知错误[2]);} }3.在AddressServiceTests测试类编写并执行单元测试。 Test public void setDefault() {try {Integer aid 13;Integer uid 27;String username 系统管理员;addressService.setDefault(aid, uid, username);System.out.println(OK.);} catch (ServiceException e) {System.out.println(e.getClass().getSimpleName());System.out.println(e.getMessage());} }3 默认收货地址-控制器 3.1 处理异常 在BaseController类中添加处理AddressNotFoundException和AccessDeniedException的异常。 // ... else if (e instanceof AddressNotFoundException) {result.setState(4004); } else if (e instanceof AccessDeniedException) {result.setState(4005); } // ...3.2 设计请求 设计用户提交的请求并设计响应的方式。 请求路径/addresses/{aid}/set_default 请求参数PathVaraible(aid) Integer aid, HttpSession sesion 请求类型POST 响应结果JsonResultVoidREST即表述性状态传递Representational State Transfer简称REST是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式可以降低开发的复杂性提高系统的可伸缩性。 3.3 处理请求 1.在AddressController类中添加处理请求的setDefault(PathVariable(“aid”) Integer aid, HttpSession session)方法。 RequestMapping({aid}/set_default) public JsonResultVoid setDefault(PathVariable(aid) Integer aid, HttpSession session) {Integer uid getUidFromSession(session);String username getUsernameFromSession(session);addressService.setDefault(aid, uid, username);return new JsonResultVoid(OK); }2.完成后启动项目打开浏览器先登录再访问http://localhost:8080/addresses/13/set_default进行测试。 4 默认收货地址-前端页面 1.在address.html页面中body标签内部的script标签内添加设置用户默认收货地址的代码。 function setDefault(aid) {$.ajax({url: /addresses/ aid /set_default,type: POST,dataType: JSON,success: function(json) {if (json.state 200) {showAddressList();} else {alert(设置默认收货地址失败 json.message);}},error: function(xhr) {alert(您的登录信息已经过期请重新登录HTTP响应码 xhr.status);location.href login.html;}}); }2.给showAddressList()方法中的“设为默认”超链接按钮添加设置默认收货地址的点击事件。 tda onclicksetDefault(#{aid}) classbtn btn-xs add-def btn-default设为默认/a/td3.完成后启动项目打开浏览器先登录再访问http://localhost:8080/web/address.html页面点击“设为默认”超链接按钮进行功能测试。 删除收货地址 1 删除收货地址-持久层 1.1 规划需要执行的SQL语句 1.在删除之前需检查数据是否存在数据归属是否正确。此功能已完成无需再次开发。 2.删除指定的收货地址的SQL语句大致是。 delete from t_address where aid?3.如果删除的这条数据是默认收货地址则应该将剩余的收货地址中的某一条设置为默认收货地址可以设定规则“将最近修改的设置为默认收货地址”要实现此功能就必须要知道“最近修改的收货地址的id是多少”。则通过以下查询语句完成。 select * from t_address where uid? order by modified_time desc limit 0,14.在执行以上操作之前还需检查该用户的收货地址数据的数量如果删除的收货地址是最后一条收货地址则删除成功后无需再执行其他操作。统计收货地址数量的功能此前已经完成无需再次开发。 1.2 接口与抽象方法 在AddressMapper接口中添加抽象方法。 /*** 根据收货地址id删除数据* param aid 收货地址id* return 受影响的行数*/ Integer deleteByAid(Integer aid);/*** 查询某用户最后修改的收货地址* param uid 归属的用户id* return 该用户最后修改的收货地址如果该用户没有收货地址数据则返回null*/ Address findLastModified(Integer uid);1.3 配置SQL映射 1.在AddressMapper.xml文件中添加以上两个抽象方法的映射。 !-- 根据收货地址id删除数据Integer deleteByAid(Integer aid) -- delete iddeleteByAidDELETE FROMt_addressWHEREaid#{aid} /delete!-- 查询某用户最后修改的收货地址Address findLastModified(Integer uid) -- select idfindLastModified resultMapAddressEntityMapSELECT*FROMt_addressWHEREuid#{uid}ORDER BYmodified_time DESCLIMIT 0,1 /select2.在AddressMapperTests测试类中添加单元测试方法。 Test public void deleteByAid() {Integer aid 4;Integer rows addressMapper.deleteByAid(aid);System.out.println(rows rows); }Test public void findLastModified() {Integer uid 30;Address result addressMapper.findLastModified(uid);System.out.println(result); }2 删除收货地址-业务层 2.1 规划异常 在执行删除操作时可能会删除数据失败此时抛出DeleteException异常。在创建com.cy.store.service.ex.DeleteException异常类并继承自ServiceException类。 package com.cy.store.service.ex;/** 删除数据失败的异常 */ public class DeleteException extends ServiceException {// Override Methods... }2.2 接口与抽象方法 在IAddressService接口中添加删除收货地址的抽象方法。 /*** 删除收货地址* param aid 收货地址id* param uid 归属的用户id* param username 当前登录的用户名*/ void delete(Integer aid, Integer uid, String username);2.3 实现抽象方法 1.在AddressServiceImpl实现类中实现以上两个抽象方法。 Transactional Override public void delete(Integer aid, Integer uid, String username) {// 根据参数aid调用findByAid()查询收货地址数据Address result addressMapper.findByAid(aid);// 判断查询结果是否为nullif (result null) {// 是抛出AddressNotFoundExceptionthrow new AddressNotFoundException(尝试访问的收货地址数据不存在);}// 判断查询结果中的uid与参数uid是否不一致(使用equals()判断)if (!result.getUid().equals(uid)) {// 是抛出AccessDeniedException非法访问throw new AccessDeniedException(非常访问);}// 根据参数aid调用deleteByAid()执行删除Integer rows1 addressMapper.deleteByAid(aid);if (rows1 ! 1) {throw new DeleteException(删除收货地址数据时出现未知错误请联系系统管理员);}// 判断查询结果中的isDefault是否为0if (result.getIsDefault() 0) {return;}// 调用持久层的countByUid()统计目前还有多少收货地址Integer count addressMapper.countByUid(uid);// 判断目前的收货地址的数量是否为0if (count 0) {return;}// 调用findLastModified()找出用户最近修改的收货地址数据Address lastModified addressMapper.findLastModified(uid);// 从以上查询结果中找出aid属性值Integer lastModifiedAid lastModified.getAid();// 调用持久层的updateDefaultByAid()方法执行设置默认收货地址并获取返回的受影响的行数Integer rows2 addressMapper.updateDefaultByAid(lastModifiedAid, username, new Date());// 判断受影响的行数是否不为1if (rows2 ! 1) {// 是抛出UpdateExceptionthrow new UpdateException(更新收货地址数据时出现未知错误请联系系统管理员);} }2.在AddressServiceTests测试类中添加单元测试方法。 Test public void delete() {try {Integer aid 18;Integer uid 30;String username 明明;addressService.delete(aid, uid, username);System.out.println(OK.);} catch (ServiceException e) {System.out.println(e.getClass().getSimpleName());System.out.println(e.getMessage());} }3 删除收货地址-控制器 3.1 处理异常 在BaseController类中添加DeleteException异常的处理。 // ... else if (e instanceof DeleteException) {result.setState(5002); } // ...3.2 设计请求 设计用户提交的请求并设计响应的方式。 请求路径/addresses/{aid}/delete 请求参数PathVariable(aid) Integer aid, HttpSession session 请求类型POST 响应结果JsonResultVoid3.3 处理请求 1.在AddressController类中添加处理请求的delete()方法。 RequestMapping({aid}/delete) public JsonResultVoid delete(PathVariable(aid) Integer aid, HttpSession session) {Integer uid getUidFromSession(session);String username getUsernameFromSession(session);addressService.delete(aid, uid, username);return new JsonResultVoid(OK); }2.完成后启动项目打开浏览器先登录再访问http://localhost:8080/addresses/26/delete进行测试。 4 删除收货地址-前端页面 1.在address.html页面中body标签内部的script标签内添加设置用户删除收货地址的代码。 function deleteByAid(aid) {$.ajax({url: /addresses/ aid /delete,type: POST,dataType: JSON,success: function(json) {if (json.state 200) {showAddressList();} else {alert(删除收货地址失败 json.message);}},error: function(json) {alert(您的登录信息已经过期请重新登录HTTP响应码 json.status);location.href login.html;}}); }2.给showAddressList()方法中的“设为默认”超链接按钮添加设置默认收货地址的点击事件。 tda onclickdeleteByAid(#{aid}) classbtn btn-xs add-del btn-infospan classfa fa-trash-o/span 删除/a/td3.完成后启动项目打开浏览器先登录再访问http://localhost:8080/web/address.html页面点击“删除”超链接按钮进行功能测试。 { System.out.println(e.getClass().getSimpleName()); System.out.println(e.getMessage()); } } ### 3 删除收货地址-控制器#### 3.1 处理异常在BaseController类中添加DeleteException异常的处理。java // ... else if (e instanceof DeleteException) {result.setState(5002); } // ...3.2 设计请求 设计用户提交的请求并设计响应的方式。 请求路径/addresses/{aid}/delete 请求参数PathVariable(aid) Integer aid, HttpSession session 请求类型POST 响应结果JsonResultVoid3.3 处理请求 1.在AddressController类中添加处理请求的delete()方法。 RequestMapping({aid}/delete) public JsonResultVoid delete(PathVariable(aid) Integer aid, HttpSession session) {Integer uid getUidFromSession(session);String username getUsernameFromSession(session);addressService.delete(aid, uid, username);return new JsonResultVoid(OK); }2.完成后启动项目打开浏览器先登录再访问http://localhost:8080/addresses/26/delete进行测试。 4 删除收货地址-前端页面 1.在address.html页面中body标签内部的script标签内添加设置用户删除收货地址的代码。 function deleteByAid(aid) {$.ajax({url: /addresses/ aid /delete,type: POST,dataType: JSON,success: function(json) {if (json.state 200) {showAddressList();} else {alert(删除收货地址失败 json.message);}},error: function(json) {alert(您的登录信息已经过期请重新登录HTTP响应码 json.status);location.href login.html;}}); }2.给showAddressList()方法中的“设为默认”超链接按钮添加设置默认收货地址的点击事件。 tda onclickdeleteByAid(#{aid}) classbtn btn-xs add-del btn-infospan classfa fa-trash-o/span 删除/a/td3.完成后启动项目打开浏览器先登录再访问http://localhost:8080/web/address.html页面点击“删除”超链接按钮进行功能测试。
http://www.ihoyoo.com/news/119633.html

相关文章:

  • 一个网站建设多少钱?c 开发微网站开发
  • 你的网站尚未进行备案游昕手游代理平台
  • 宝安-网站建设信科网络天河做网站平台
  • 高端网站建设 杭州wordpress 游戏主题下载失败
  • 做网站帮京东卖东西怎么合作wordpress改网站名字
  • 国内做的好的电商网站有哪些方面网站建设q-9
  • 做网站价格miniuinet室内设计效果图素材
  • 犀牛云网站建设怎么样一建 建设网站
  • 卖友情链接的哪来那么多网站网站里面如何做下载的app
  • 网站登录入口网页wordpress恢复数据库文件
  • 移动应用开发公司网站模板1688外贸网站
  • 网站公司成本网站右侧二维码
  • 个人博客网站实验报告在线教育自助网站建设平台
  • 男女直接做的视频 视频网站做的很不好的网站
  • 唯品会一家专做特卖的网站山东省住房建设厅网站考试项目
  • cdn网站网络加速器如何自己建个网站
  • 华为建站有关网站开发的文献或论文
  • 校园网站建设的优点网站功能配置
  • 网站备案的公司注销了wordpress开发手册中文
  • 医疗器械网站建设方案做网站需准备些什么
  • 国外的网站建设公司可能wordpress.org或服务器配置文件存在问题
  • 下载 做网站的原型文件在线画画
  • 辽阳企业网站建设费淮北之窗
  • 北京住房和建设部网站首页湖南网络大课堂
  • 12380网站建设意见焦作网站建设设计公司
  • 建设政务网站wordpress文章提交
  • 加强网站的建设与管理做网站销售电话术语
  • 湖州民生建设有限公司网站wordpress在线时间获取
  • 购物的网站功能深圳最新项目
  • wordpress淘宝客类网站建设国内便宜机票网站建设