Spring Boot -- 图书管理系统(登录、展示+翻页、添加/修改图书)

文章目录

  • 一、应用分层
  • 二、数据库的设计
  • 三、登录功能
  • 四、展示列表(使用虚构的数据)
  • 五、翻页 + 展示功能
  • 六、添加图书
  • 七、修改图书

一、应用分层

  1. 为什么我们需要应用分层:当代码量很多时,将其全部放在一起查找起来就会很麻烦,而且不利于开发。
  2. 分层思想:两种的侧重点不同,三层架构侧重于对数据的处理
    • MVC:Controller层接收返回数据、Model层进行具体的业务处理、View层返回视图。但随着前后端分离,MVC分层思想已经不适用了。
    • 三层架构:是目前比较主流的分层方法。如果项目十分复杂,也可以在该架构的基础上分得再细一点
      • 表现层:接收并返回请求
      • 业务逻辑层:处理业务逻辑
      • 数据层:处理数据,包括数据的存储与获取
  3. 如何分层:Spring支持了三层架构,我们只需要用文件夹分层即可
    • 表现层 <------> Controller ---------》接收请求 + 数据的初步校验 + 结果响应
    • 业务逻辑层 <------> Service ---------》真正干活的部分
    • 数据层 <------> Dao ---------》用来进行DB处理
  4. 关于实体类:一般把实体类都放在【model】文件夹里

二、数据库的设计

  1. 表通常分为实体类和关系表
    • 实体类:根据需求中会出现的的名词进行设计,比如用户表、博客表、图书表
      • 注意,除了需求的字段,建议加上id、update_time、create_time、deleteFlaf着几个字段
    • 关系表:表示了实体类之间的关联关系
      • 如果关系比较简单,也是可以放在实体表中的
  2. user_info表
    • 表名和字段名要用反引号(`),而不是单引号(')
DROP TABLE IF EXISTS book_info;
CREATE TABLE `book_info` (`id` INT(11) NOT NULL AUTO_INCREMENT,`book_name` VARCHAR(127) NOT NULL,`author` VARCHAR(127) NOT NULL,`count` INT(11),`price` DECIMAL(7,2) NOT NULL,`publish` VARCHAR(256),`status` TINYINT(4) DEFAULT 1 COMMENT '0-无效,1-正常,2-不允许借阅,类似于delete_flag',`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now() COMMENT '创建默认用当前时间,更新时用当前时间更新',PRIMARY KEY(`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;

三、登录功能

  1. 前端代码
<script>function login() {var userName = $('#userName').val();var password = $('#password').val();if (userName=='' || password==''){return;}$.ajax({url: "/user/login",type: "post",data: {"userName": userName,"password": password},success: function (result){if (result){location.href = "/book_list.html";}else{alert("用户名或密码错误");}}});}
</script>
  1. 后端代码

Controller层

@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/login")public boolean login(String userName, String password, HttpSession session){//账号或密码为空if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){return false;}UserInfo userInfo = userService.queryUserByName(userName);if (userInfo == null || userInfo.getId() <= 0){//如果找不到或者结果不对,就返回falsereturn false;}//账号密码正确if(userInfo!=null && password.equals(userInfo.getPassword())){//存储在Session中userInfo.setPassword("");//如果不指向存储用户名字,想知道更多信息,可以把整个userInfo存进去session.setAttribute("session_user_key",userInfo);return true;}return false;}
}

Service层

@Service
public class UserService {@Autowiredprivate UserInfoMapper userInfoMapper;public UserInfo queryUserByName(String name) {return userInfoMapper.queryUserByName(name);}
}

model 层

@Data
public class UserInfo {private Integer id;private String userName;private String password;private Integer deleteFlag;private Date creatTime;private Date updateTime;
}

mapper层

@Mapper
public interface UserInfoMapper {@Select("select * from user_info where delete_flag=0 and user_name=#{name}")UserInfo queryUserByName(String name);
}

四、展示列表(使用虚构的数据)

  1. 实体类设置
    • BigDecimal:价格相关的一般会涉及到精度,故而不用float或double
    • status VS statusCN
      • 状态我们一般不存储中文,因为案例中总共也只有【已借阅】和【未借阅】这两种状态,与其存中文,我们干脆直接存数字。到时候根据状态的数字,设置中文描述
      • 而且万一修改了状态的需求,那就需要修改整个数据库(比如将所有"已借阅"改为"审批中")。企业中数据库的修改由于数据量大,其数据修改是一件很麻烦的事
        • 也正因如此,我们定义数据库时,无需节约,尽量往大了定义。
    • 关于状态的中文描述设置:这个工作前后端都可以完成,此处我们由后端完成
@Data
public class BookInfo {private Integer id;private String bookName;private String author;private Integer count;private BigDecimal price;private String publish;private Integer status;private String statusCN;
}
  1. 后端代码

Controller层
(1)为什么不在方法内部设置BookService属性:这个类该Controller层的所有方法都要用,提出来就不需要每个方法反复创建了

@RestController
@RequestMapping("/book")
public class BookController {@Autowiredprivate BookService bookService;@RequestMapping("getList")public List<BookInfo> getList(){return bookService.getList();}
}

Service层

@Component
public class BookService {@Autowiredprivate BookDao bookDao;public List<BookInfo> getList(){List<BookInfo> bookInfos = bookDao.mockData();for (BookInfo book : bookInfos){if (book.getStatus() == 1){book.setStatusCN("已借阅");}else{book.setStatusCN("不可借阅");}}return bookInfos;}
}

Dao层
(1)优化tip:如果知道要创建list的具体长度,创建时直接写上,这样后面就不需要再扩容了
(2)mockData:虚构数据

@Component
public class BookDao {private List<BookInfo> bookInfos = new ArrayList<>(10);public List<BookInfo> mockData(){for (int i = 0; i < 10; i++){BookInfo book = new BookInfo();book.setId(i);book.setBookName("图书" + i);book.setAuthor("作者" + i);//随机生成一个200以下的整数book.setCount(new Random().nextInt(200));//随机生成一个100以下的小数book.setPrice(new BigDecimal(new Random().nextInt(100)));book.setPublish("出版社" + i);book.setStatus(i % 2 == 0?2:1);bookInfos.add(book);}return bookInfos;}
}
  1. 前端代码
    • 代码的执行:JS代码是从上往下执行的,前面语法之类的出错了,后面的代码就不会执行了
    • 关于引号:因为前端代码一些class是用双引号括起来,所以我们用单引号构造HTML,以便区分
    • 关于添加数值:先写一对单引号,让它们前后去闭合,然后添加book.id之类的数值
function getBookList() {
$.ajax({url: "/book/getList",type: "get",success: function (books){var divE = '';for (var book of books){console.log("打印");divE += '<tr>';divE += '<td><input type=\"checkbox\" name=\"selectBook\" value=\"' + book.id+ '\" id=\"selectBook\" class=\"book-select\"></td>';divE += '<td>'+ book.id + '</td>';divE += '<td>'+book.bookName +'</td>';divE += '<td>'+ book.author + '</td>';divE += '<td>'+ book.count + '</td>';divE += '<td>'+ book.price + '</td>';divE += '<td>'+ book.publish + '</td>';divE += '<td>'+ book.statusCN + '</td>';divE += '<td>';divE += '<div class="op">';divE += '<a href=\"book_update.html?bookId='+ book.id + '\">修改</a>';divE += '<a href=\"book_update.html?bookId='+ book.id + '\">修改</a>';divE += '</div>';divE += '</td>';divE += '</tr>';}$("tbody").html(divE);}
});

五、翻页 + 展示功能

  1. 功能的实现:可以由后端完成,也可以由前端完成
    • 前端完成:可以一次性拿到所有的数据
      • 缺点
        • 第一次请求响应时间长
        • 数据如果进行修改,前端无法感知到
      • 优点:因为一次性获取了所有数据,后续翻页不需要去调用后端,后续响应快
    • 后端完成:需要多次请求。推荐
      • 第一次请求时,请求第一页的内容,后端只需要返回第一页的信息即可。请求第二页的内容,后端只返回第二页的内容
  2. 如何实现该功能
    • SQL对返回的语句进行限制:select * from book_info limit $(offset), $(limit)
      • offset:从第几条数据开始返回。需要计算,(currentpasge - 1) * pageSize
      • limit:返回几条数据
    • 前端需要告诉我们的内容
      • 当前页:currentPage
      • 每页显示的条数:因为可能会改需求,所以此处我们并不写死
    • 后端返回的结果
      • 当前页的内容
      • 总条数:如果一共只有24条数据,且每页展示10条,此时最多翻3页,多了数据不够查不到
    • 使用翻页插件:翻页插件有很多,此处我们使用 jqPaginator
  3. controller层与model层的设计
    • 用对象封装:controller层因为涉及到了接口文档的编写,有沟通成本,一旦增加新需求,就需要改文档了(其他service层,dao层无所谓是否用对象)
      • 所以,为了方便后续进行扩展,返回值和参数建议设置一个类
    • 潜在问题:当我们查看完一页内容(默认显示5条),又去设置显示10条,此时就会出现的数据是
@RequestMapping("/book")
@RestController
public class BookController {@RequestMapping("/getBookListByPage")public PageResult getBookListByPage(PageRequest pageRequest){return null;}
}
@Data
public class PageRequest {/*** 当前是那一页,如果没传默认取第一页*/private int currentPage = 1;/*** 展示几条记录,如果没传默认展示5条*/private int pageSize = 5;
}
@Data
public class PageResult {/*** 当前页的记录*/private List<BookInfo> records;/*** 总记录数*/private Integer total;
}
  1. service的封装问题
    在这里插入图片描述
  2. 后端代码
    • try-catch:处理异常问题
    • 封装操作
    • 使用@slif4j多打印日志:在开发阶段可以多多打印日志,这样我们可以根据日志来判断错误,这样就不用debug了

Controller层

@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {@AutowiredBookService bookService;@RequestMapping("/getBookListByPage")public PageResult<BookInfo> getBookListByPage(PageRequest pageRequest){log.info("查询翻页信息,pageRequest:{}", pageRequest);if (pageRequest.getPageSize() < 0 || pageRequest.getCurrentPage() < 1){return null;}PageResult<BookInfo> res = null;try {res = bookService.selectBookInfoByPage(pageRequest);}catch (Exception e){log.error("查询翻页信息错误");}return res;}
}

Service层
(1)为什么要返回pageRequest:前端翻页要用

(2)使用线程池优化:可以搞两个线程,一个去获取当前页的内容,一个去获取总记录数

@Slf4j
@Service
public class BookService {@AutowiredBookInfoMapper bookInfoMapper;public PageResult selectBookInfoByPage(PageRequest pageRequest){if (pageRequest == null){return null;}log.info(pageRequest.getCurrentPage() + "");List<BookInfo> bookInfos = bookInfoMapper.selectBookInfoByPage(pageRequest.getOffset(), pageRequest.getPageSize());for (BookInfo bookInfo: bookInfos){if (bookInfo.getStatus() == 1){bookInfo.setStatusCN("可借阅");}else if (bookInfo.getStatus() == 2){bookInfo.setStatusCN("不可借阅");}if(bookInfo.getPublish() == null){bookInfo.setPublish("不干人事出版社");}}Integer count = bookInfoMapper.count();return new PageResult(bookInfos, count, pageRequest);}
}

(3)使用枚举类进行优化
在这里插入图片描述

//枚举类
public enum BookStatusEnum {DELETED(0, "删除"),NORMAL(1, "可借阅"),FORBINNEN(2, "不可借阅");/*** 状态,对应了BookInfo里的status*/private int code;/*** 状态对应的中文,对应了BookInfo里的statusCN*/private String name;BookStatusEnum(int code, String name) {this.code = code;this.name = name;}public String getName() {return name;}/*** 根据 code获取对应的 BookStatusEnum* 使用static,这样就不需要调用时专门去new一个对象了* @return*/public static BookStatusEnum getNameByCode(int code){switch (code){case 0: return BookStatusEnum.DELETED;case 1: return BookStatusEnum.NORMAL;case 2: return BookStatusEnum.FORBINNEN;default:return BookStatusEnum.FORBINNEN;}}
}
@Slf4j
@Service
public class BookService {@AutowiredBookInfoMapper bookInfoMapper;public PageResult selectBookInfoByPage(PageRequest pageRequest){if (pageRequest == null){return null;}log.info(pageRequest.getCurrentPage() + "");List<BookInfo> bookInfos = bookInfoMapper.selectBookInfoByPage(pageRequest.getOffset(), pageRequest.getPageSize());if (bookInfos != null && bookInfos.size() > 0){for (BookInfo bookInfo: bookInfos){//根据status获取状态的定义bookInfo.setStatusCN(BookStatusEnum.getNameByCode(bookInfo.getStatus()).getName());}}Integer count = bookInfoMapper.count();return new PageResult(bookInfos, count, pageRequest);}
}

model层
(1) 接口参数的定义:实际开发中,只设置接收参数offset、limit即可,但这里因为后端不处理,就要由前端处理,当前情况下,用前端处理比较麻烦,故而后端来处理。

@Data
public class PageRequest {/*** 首先进入时是在哪里,如果没传默认取第一页*/private int currentPage = 1;/*** 展示几条记录,如果没传默认展示5条*/private int pageSize = 5;private Integer offset;public Integer getOffset() {return (currentPage - 1) * pageSize;}
}
@Data
public class PageResult<T> {/*** 当前页的记录*/private List<BookInfo> records;/*** 总记录数*/private Integer total;private PageRequest request;public PageResult(List<BookInfo> records, Integer total, PageRequest request) {this.records = records;this.total = total;this.request = request;}
}

Mapper层

@Mapper
public interface BookInfoMapper {/*** 获取当前页的信息* @param offset 从第几条数据开始算* @param pageSize  一次性展示多少数据* @return*/@Select("select * from book_info where status != 0 limit #{offset}, #{pageSize}")List<BookInfo> selectBookInfoByPage(Integer offset, Integer pageSize);/*** 获取总记录数* @return*/@Select("select count(1) from book_info where status != 0")Integer count();
}
  1. 前端代码
    • location.href = “book_list.html?currentPage=” + page:如果有翻页操作,就会执行跳转,跳转的url是【book_list.html?currentPage=xxx】。同时【book_list.html】这个页面会在进入时,执行前端的 getBookList() 方法,以获得当前页的数据
    • url: “/book/getBookListByPage” + location.search
      • 这是【getBookList()】的ajax的url,【location.search】获取的是查询字符串的值,即【?currentPage=xxx】
      • 当执行了翻页操作后,就会有【查询字符串+来到book_list页面+再次执行了getBookList方法】
<script>getBookList();function getBookList() {$.ajax({type: "get",url: "/book/getBookListByPage" + location.search,success: function (result) {var finalHtml = "";//加载列表//var pageResult = result.data;for (var book of result.records) {//根据每一条记录去拼接html, 也就是一个trfinalHtml += '<tr>';finalHtml += '<td><input type="checkbox" name="selectBook" value="' + book.id + '" id="selectBook" class="book-select"></td>';finalHtml += '<td>' + book.id + '</td>';finalHtml += '<td>' + book.bookName + '</td>';finalHtml += '<td>' + book.author + '</td>';finalHtml += '<td>' + book.count + '</td>';finalHtml += '<td>' + book.price + '</td>';finalHtml += '<td>' + book.publish + '</td>';finalHtml += '<td>' + book.statusCN + '</td>';finalHtml += '<td><div class="op">';finalHtml += '<a href="book_update.html?bookId=' + book.id + '">修改</a>';finalHtml += '<a href="javascript:void(0)" οnclick="deleteBook(' + book.id + ')">删除</a>';finalHtml += '</div></td></tr>';}$("tbody").html(finalHtml);//翻页信息$("#pageContainer").jqPaginator({totalCounts: result.total, //总记录数pageSize: 5,    //每页的个数visiblePages: 5, //可视页数currentPage: result.request.currentPage,  //当前页码first: '<li class="page-item"><a class="page-link">首页</a></li>',prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页<\/a><\/li>',next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页<\/a><\/li>',last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页<\/a><\/li>',page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',//页面初始化和页码点击时都会执行onPageChange: function (page, type) {console.log("第" + page + "页, 类型:" + type);if (type == "change") {location.href = "book_list.html?currentPage=" + page;}}});},error: function (error) {console.log(error);if (error.status == 401) {console.log("401");location.href = "login.html";}}});}
</script>
  1. 处理BigDecimal的精度问题
    • 问题的描述
      • 因为price的类型设置为BigDecimal,如果价钱是22.18,前端不会忽略,如果是22.00,前端展示时会被忽略,只展示22
      • 注意,后端的返回是没有问题的,是可以正确显示出22.00的,这就是前端的显示问题
    • 解决方法
      • 前端解决:这本来就是前端的显示问题,前端专门去修改一下即可
      • 后端解决:后端把返回的格式改为一个字符串,这样就不会被忽略了
    • 如何使用后端来解决—>如何把返回格式设置为字符串
      @JsonFormat(shape = JsonFormat.Shape.STRING),将展示的形态格式化处理,此处设置成了字符串类型
@Data
public class BookInfo {private Integer id;private String bookName;private String author;private Integer count;@JsonFormat(shape = JsonFormat.Shape.STRING)private BigDecimal price;private String publish;private Integer status;private String statusCN;private Date createTime;private Date updateTime;
}

六、添加图书

  1. 后端返回情况选择:告诉前端是否添加成功。第三种更为合理,但为了方便此处我们采用第二种。
    • boolean:true表示添加成功,false表示添加失败
      • 缺点:没法告诉前端,失败的原因是什么
    • String:“”表示添加成功,不为空则是添加失败
    • 对象:boolean类型的result用来表示【是否添加成功】。String类型的errorMsg用来表示【错误原因】
  2. 关于日志的打印:没有异常出错的代码,日志打不打以及在哪里打看个人意愿,但是异常/出错的情况,是需要打日志的,比如密码输入错误,账号不存在等
    • 好处:有了日志,我们就可以减少debug的次数,可以直接根据debug判断错误的点
  3. 关于异常的捕获:service层、controller层选一个 try-catch处理 就好了
    • 如果service层处理了,controller层没必要再处理一次
    • 如果service层没处理,异常会抛给controller层,到时候由controller层处理即可
  4. 关于controller层的参数校验
    在这里插入图片描述
  5. 后端代码
    Controller层
@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {@AutowiredBookService bookService;@RequestMapping("/addBook")public String addBook(BookInfo bookInfo){log.info("接收到添加图书请求,bookinfo:{}", bookInfo);if (bookInfo == null||!StringUtils.hasLength(bookInfo.getBookName())|| !StringUtils.hasLength(bookInfo.getAuthor())|| bookInfo.getCount() < 0|| bookInfo.getPrice() == null|| !StringUtils.hasLength(bookInfo.getPublish())){log.error("传参错误,bookInfo:{}", bookInfo);return "参数校验失败,请检查入参";}Integer res = bookService.addBook(bookInfo);if (res <= 0){log.error("添加图书出错:bookInfo:{}", bookInfo);return "添加图书出错,请联系管理员";}return "";}
}

Service层

@Slf4j
@Service
public class BookService {@AutowiredBookInfoMapper bookInfoMapper;public Integer addBook(BookInfo bookInfo){Integer result = 0;try{result = bookInfoMapper.addBook(bookInfo);if (result <= 0){log.error("service 层添加图书失败:bookInfo:{}",bookInfo);}}catch (Exception e){log.error("添加图书失败,e:{}",e);}return result;}
}

Model层

@Data
public class BookInfo {private Integer id;private String bookName;private String author;private Integer count;@JsonFormat(shape = JsonFormat.Shape.STRING)private BigDecimal price;private String publish;private Integer status;private String statusCN;private Date createTime;private Date updateTime;
}

Mapper层

@Mapper
public interface BookInfoMapper {@Insert("insert into book_info(book_name, author, count, price, publish, status)" +"values(#{bookName}, #{author}, #{count}, #{price}, #{publish}, #{status})")Integer addBook(BookInfo bookInfo);
}
  1. 前端代码
    • $(addBook).serialize():提交整个form表单,form标签包裹的input、select、textarea等输入信息都会被提交
      • 传过去的格式在这里插入图片描述
    • 优点:没必要像下面这样,把所有要传过去的数据都写一遍,可以一行代码搞定
data:{bookName: $("#bookName").val(),xxxxx
}
<script>function add() {$.ajax({url: "/book/addBook",type: "post",data: $(addBook).serialize(),success: function (result){if (result == ""){location.href = "book_list.html";}else{alert(result);}}})}
</script>

七、修改图书

  1. 需求
    • 点击修改按钮时,能把当前图书的信息显示出来
      • 我们可以根据url上的bookId查到对应的BookInfo
    • 点击确定时,能把修改的结果进行保存
  2. 后端代码
    Controller层
@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {@AutowiredBookService bookService;@RequestMapping("/queryBookInfoById")public BookInfo queryBookInfoById(Integer bookId){log.info("根据id查询图书,bookId:{}", bookId);try{BookInfo bookInfo = bookService.queryBookById(bookId);return bookInfo;}catch (Exception e){log.error("查询图书失败,e:{}", e);}return null;}@RequestMapping("/updateBook")public String updateBook(BookInfo bookInfo){log.info("接收到的bookInfo:{}", bookInfo);Integer result = bookService.updateBook(bookInfo);if (result == 0){log.error("更新图书失败,请联系管理员");return "失败";}return "";}
}

Service层

@Slf4j
@Service
public class BookService {@AutowiredBookInfoMapper bookInfoMapper;public BookInfo queryBookById(Integer id){return bookInfoMapper.queryBookById(id);}public Integer updateBook(BookInfo bookInfo){Integer res = 0;try{res = bookInfoMapper.updateBook(bookInfo);}catch (Exception e){log.error("更新图书失败,e:{}", e);}return res;}
}

Mapper层

  • 配置yml + 准备文件
    在这里插入图片描述
@Mapper
public interface BookInfoMapper {@Select("select * from book_info where id = #{id}")BookInfo queryBookById(Integer id);//涉及到动态sql,使用xml的方式去实现Integer updateBook(BookInfo bookInfo);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.book_test.Mapper.BookInfoMapper"><update id="updateBook">  <!-- 建议先写简单的SQL,再在其基础上将其改为动态SQL-->update book_info<set><if test="bookName != null">book_name = #{bookName},</if><if test="author != null">author = #{author},</if><if test="count != null">count = #{count},</if><if test="price != null">price = #{price},</if><if test="publish != null">publish = #{publish},</if><if test="status != null">status = #{status}</if></set>where id = #{id};<!--此处不用where标签,因为此处where和id是一定要存在的--><!--如果使用了where标签,当没有传id时,where会被删掉,不会显示地报错--></update>
</mapper>
  1. 前端代码
    • $(“#bookName”),val():val方法里,如果里面没有值,会根据选择器的设置去拿值,如此处就是去找id为bookName的选择器,获取里面的值
    • $(“#bookName”),val(book.bookName):val方法里,如果里面有值,就是把括号里的值赋值给选中的对象
    • < input type=“hidden” class=“form-control” id=“bookId” name=“id”>:hidden表示这个文本框是被隐藏的,浏览器上看不到,但是里面可以有值
      • 为什么要加上这个代码:使用location.search获取到的值是【?bookId = xxx】,但有时我们只想要获得这个数字。下面提供了两种解决方法,由于方法一比较困难,我们采用方法二
        (1)解决方法一:通过【去掉问号 + 用等号分割】获得了这个数字

        (2)解决方法二:使用一个隐藏的文本框,这样前端把整个表单传给后端时,后端也能收到bookid了

 <input type="hidden" class="form-control" id="bookId" name="id">
<script>$.ajax({type: "get",url: "/book/queryBookInfoById" + location.search,success: function (book){if (book != null) {//页面输入框的填充$("#bookId").val(book.id);$("#bookName").val(book.bookName);$("#bookAuthor").val(book.author);$("#bookStock").val(book.count);$("#bookPrice").val(book.price);$("#bookPublisher").val(book.publish);$("#bookStatus").val(book.status)} else {alert("图书不存在")}}});function update() {$.ajax({type: "post",url: "/book/updateBook",data: $("#updateBook").serialize(),success: function (result) {if (result != null) {location.href = "book_list.html";} else {alert(result);}}});}</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/30947.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker 可用镜像源

当使用 docker 发现拉取不到镜像时&#xff0c;可以编辑 /etc/docker/daemon.json 文件&#xff0c;添加如下内容&#xff1a; 这文章不涉及政治&#xff0c;不涉及敏感信息&#xff0c;三番五次的审核不通过&#xff0c;一删再删&#xff0c;只好换图片了。 重新加载服务配置…

Zookeeper基础教程

Zookeeper基础教程 资料来源&#xff1a;Zookeeper Tutorial (tutorialspoint.com) zookeeper就是Hadoop生态动物园的管理员 1. Zookeeper-概述 ZooKeeper是一种分布式协调服务&#xff0c;用于管理大型主机集群(large set of hosts)。在分布式环境中协调和管理服务是一个复…

解决navicat连接oracle19c数据库缺少oci.dll

下载oci.dll文件 搜索Oracle Instant Client Downloads Oracle Instant Client Downloads点击 Oracle Instant Client Downloads 超链接 根据自己的操作系统按需选择 以windows64位为例&#xff0c;下载 Version 19.23.0.0.0的OCI压缩包 解压到Navicat的安装根路径下&#xff…

Qt creator day1 练习

自由发挥登录窗口的应用场景&#xff0c;实现一个登录窗口界面&#xff0c;要求&#xff1a;第行代码都有注释 #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {this->setWindowTitle("贪玩蓝月——是兄弟就来砍我 登入&#…

两个src案例分享

案例一 文前废话:某天正在刷着**社区的帖子,欣赏着漂亮的小姐姐,突然间评论区的一条评论引起了我的注意,类似于下面这样 这种评论在html标签中代码格式是<a>这是文字</a>这样的 同时评论区XSS漏洞的高发区,想着可能会有操作点 一、发布一个标题有js语句的贴子 二…

接口联调测试工作总结

接口联调测试工作已经告一段落&#xff0c;现在总结如下: 1、首先接口联调测试的价值 2、接口联调要有工作思路 3、接口联调工作准备 4、接口联调测试数据设计 5、接口联调脚本研发 6、脚本联调测试 测试业务本身需要接口联调调用 独立接口正确&#xff0c;但有可能接口…

如何用Vue3打造一个令人惊叹的极坐标图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 使用 Vue3-ApexCharts 绘制极地区域图 应用场景 极地区域图常用于展示具有周期性或分类性数据的分布情况&#xff0c;例如不同月份的销售额、不同年龄段的人口分布等。 基本功能 此代码使用 Vue3-ApexChart…

平安养老险黄山中支开展“反洗钱电影送下乡”活动

为不断增强反洗钱教育宣传的精准性和有效性&#xff0c;提升乡村群众的洗钱风险防范意识&#xff0c;6月18日&#xff0c;在中国人民银行黄山市分行的部署和指导下&#xff0c;平安养老保险股份有限公司&#xff08;以下简称“平安养老险”&#xff09;黄山中心支公司、平安人寿…

Python基础教程(二十六):对接MongoDB

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

COSMOSPANDA星际熊猫X 2024广州童博会|聚焦星潮,潮酷无限

中国国宝级原创潮玩IP COSMOSPANDA星际熊猫 震撼亮相2024广州童博会现场 聚焦星潮&#xff0c;潮酷无限 星际熊猫亮相展馆C位 3天展期、400㎡展位 超大型潮玩原创艺术装置 潮玩艺术&#xff0c;打造强烈视觉冲击 外贸中心周善青副主任代表中国第一展广交会前来巡馆 星际…

虚拟机配置桥接模式

背景 因为要打一些awd比赛,一些扫描工具什么的,要用到kali,就想着换成一个桥接模式 但是我看网上的一些文章任然没弄好,遇到了一些问题 前置小问题 每次点开虚拟网络编辑器的时候都没有vmnet0,但是点击更改的时候却有vmnet0 第一步: 点击更改设置 第二步: 把wmnet0删掉 …

构建高效的大数据量延迟任务调度平台

目录 引言系统需求分析系统架构设计 总体架构任务调度模块任务存储模块任务执行模块 任务调度算法 时间轮算法优先级队列分布式锁 数据存储方案 关系型数据库NoSQL数据库混合存储方案 容错和高可用性 主从复制数据备份与恢复故障转移 性能优化 水平扩展缓存机制异步处理 监控与…

【代码随想录】【算法训练营】【第44天】 [322]零钱兑换 [279]完全平方数 [139]单词拆分

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 44&#xff0c;周四&#xff0c;坚持不住了~ 题目详情 [322] 零钱兑换 题目描述 322 零钱兑换 解题思路 前提&#xff1a; 思路&#xff1a; 重点&#xff1a; 代码实现 C语言 [279] 完全…

(创新)基于VMD-CNN-BiLSTM的电力负荷预测—代码+数据

目录 一、主要内容&#xff1a; 二、运行效果&#xff1a; 三、VMD-BiLSTM负荷预测理论&#xff1a; 四、代码数据下载&#xff1a; 一、主要内容&#xff1a; 本代码结合变分模态分解( Variational Mode Decomposition&#xff0c;VMD) 和卷积神经网络(Convolutional neu…

视频去水印,视频去水印软件

有时候我们在网上下载了一些喜欢的视频&#xff0c;但是却发现上面有水印&#xff0c;影响观看体验。今天我就来教大家一个轻松去除视频水印的简单的方法。 一、使用专业视频编辑软件去水印 市面上有很多专业的视频编辑软件&#xff0c;如Adobe Premiere Pro&#xff0c;它们都…

代码大模型揭秘:从下载到推理,全流程体验StarCoder

选择模型 模型榜单 大模型的发展日新月异&#xff0c;性能强劲的大模型不断涌现&#xff0c;可以实时关注开源大模型的榜单&#xff0c;选择合适自己的大模型 开源大模型榜单 开源代码大模型榜单 模型网站 目前主流的下载模型的网站就是 huggingface 全球社区&#xff0c;…

react实现窗口悬浮框,可拖拽、折叠、滚动

1、效果如下 2、如下两个文件不需要修改 drag.js import React from "react"; import PropTypes from "prop-types";export default class DragM extends React.Component {static propTypes {children: PropTypes.element.isRequired};static defaultP…

Python - 各种计算器合集【附源码】

计算器合集 一&#xff1a;极简版计算器二&#xff1a;简易版计算器三&#xff1a;不简易的计算器四&#xff1a;还可以计算器 一&#xff1a;极简版计算器 运行效果&#xff1a; import tkinter as tk import tkinter.messagebox win tk.Tk() win.title("计算器")…

Faiss:选择合适的索引Index

向量相似性搜索彻底改变了搜索领域。它允许我们高效地检索从GIF到文章等各种媒体&#xff0c;即使在处理十亿级别数据集时&#xff0c;也能在亚秒级时间内提供令人印象深刻的准确性。 然而&#xff0c;这种灵活性也带来了一个问题&#xff1a;如何知道哪种索引大小最适合我们的…

EE trade:现货黄金交易时间与操作技巧

现货黄金作为当今最为热门的投资方式之一&#xff0c;其独特的交易机制和高收益潜力吸引了大量投资者的关注。对于新手投资者而言&#xff0c;可能对于现货黄金交易完全是一片空白。因此&#xff0c;了解现货黄金的交易时间和操作技巧、掌握基本投资知识&#xff0c;是至关重要…