第三章:用户管理功能【基于Servlet+JSP的图书管理系统】

图书管理系统

在这里插入图片描述

用户管理

07-图书管理系统-用户管理-基础结构

1. 查询用户信息

1.1 流程分析

  我们需要展示的数据是sys_user表结构中的数据

在这里插入图片描述

然后对应的实现逻辑

在这里插入图片描述

1.2 代码结构

  清楚了我们要操作的数据。我们就可以来创建相关的代码。整个项目的结构我们分为com.boge.syscom.boge.book两个模块。

在这里插入图片描述

然后创建SysUser实体对象

@Data
public class SysUser {

    private Integer id;
    private String username;
    private String password;
    private String nickname;
    private Integer roleId;
    private String rolename;
    private String img;
    private Date createTime;
}

我们在这块使用了lombok来简化实体的定义。这块需要添加对应的依赖

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.2</version>
    </dependency>

然后在idea中我们需要添加lombok的插件

在这里插入图片描述

然后添加Dao的接口定义和对应的实现

/**
 * Dao层的接口对象
 */
public interface IUserDao {

    /**
     * 查询所有的用户信息
     * @return
     */
    public List<SysUser> list(SysUser user);
}

public class UserDaoImpl implements IUserDao {

    @Override
    public List<SysUser> list(SysUser user) {
        
        return null;
    }

}

1.3 DBUtils封装

  定义的Dao的实现类。然后我们就需要通过JDBC来实现对数据库表结构中数据的CRUD操作。为了简化操作我们通过Apache Dbutils来实现。那么我们定义一个公共的MyDbUtils工具类。

/**
 * 操作数据库的工具类
 */
public class MyDbUtils {

    // 定义的数据源
    private static MysqlDataSource dataSource;

    static {
        // 完成数据源的初始化操作
        dataSource = new MysqlDataSource();
        // 注意默认安装的数据的端口是 3306 这块需要结构自己数据库的情况调整
        dataSource.setUrl("jdbc:mysql://localhost:3320/books?serverTimezone=UTC");
        dataSource.setUser("root");
        dataSource.setPassword("123456");
    }

    public static QueryRunner getQueryRunner(){
        return new QueryRunner(dataSource);
    }
}

然后就可以在UserDaoImpl中通过我们提供的工具类来完成数据库的操作操作了

@Override
public List<SysUser> list(SysUser user) {
    QueryRunner queryRunner = MyDbUtils.getQueryRunner();
    String sql = "select * from sys_user ";
    try {
        // BeanListHandler 会自动的帮助我们完成字段和属性的映射。前提是属性和字段完全一直
        // 此处不会通过驼峰命名法 装换
        // List<SysUser> list = queryRunner.query(sql, new BeanListHandler<SysUser>(SysUser.class));
        List<SysUser> list = queryRunner.query(sql, new ResultSetHandler<List<SysUser>>() {
            @Override
            public List<SysUser> handle(ResultSet resultSet) throws SQLException {
                // 存储返回结果的容器
                List<SysUser> list = new ArrayList<>();
                while(resultSet.next()){
                    // 每次循环一次 user 存储一条数据
                    SysUser user = new SysUser();
                    user.setId(resultSet.getInt("id"));
                    user.setUsername(resultSet.getString("username"));
                    user.setPassword(resultSet.getString("password"));
                    user.setNickname(resultSet.getString("nickname"));
                    user.setRoleId(resultSet.getInt("role_id"));
                    user.setRolename(resultSet.getString("rolename"));
                    user.setCreateTime(resultSet.getDate("create_time"));
                    list.add(user); // 把查询的记录封装到了集合容器中
                }
                return list; // 返回查询的结果
            }
        });
        return list;
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }
    return null;
}

public static void main(String[] args) {
    IUserDao dao = new UserDaoImpl();
    List<SysUser> list = dao.list(null);
    for (SysUser sysUser : list) {
        System.out.println(sysUser);
    }
}

然后通过测试代码完成查询操作,看到如下结果表示成功

在这里插入图片描述

1.4 service

  service这块我们同样的需要定义相关的接口和实现类

/**
 * 系统用户
 * 业务处理的接口定义
 */
public interface IUserService {

    public List<SysUser> list(SysUser user);
}

public class UserServiceImpl implements IUserService {

    private IUserDao dao = new UserDaoImpl();

    @Override
    public List<SysUser> list(SysUser user) {
        return dao.list(user);
    }
}

1.5 Servlet

  servlet的作用是接口浏览器传递的请求。然后根据service的处理把相关的结果响应给浏览器。我们基于Servlet的规范来创建对应的Servlet。那么Servlet需要继承 HttpServlet 然后我们重写对应的doGetdoPost方法。

/**
 * urlPatterns:这个是提供给客户端访问Servlet的请求路径
 */
@WebServlet(name = "userServlet",urlPatterns = {"/userServlet"})
public class UserServlet extends HttpServlet {

    private IUserService service = new UserServiceImpl();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 查询所有的用户信息
        List<SysUser> list = service.list(null);
       // System.out.println(list);
        // 把数据绑定在对应的作用域中
        req.setAttribute("list",list);
        req.getRequestDispatcher("/sys/user/list.jsp").forward(req,resp);
    }
}

1.6 页面展示

  数据展示页面我们通过sys/user/list.jsp来实现。注意我们需要调整web.xml中的版本信息。

在这里插入图片描述

EL表达式没有被识别,我们需要更新web.xml中的信息

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
</web-app>

然后我们通过jstl标签来实现数据的呈现。

<%--
  weChat:boge_java
  QQ:279583842
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core"  prefix="c"%>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>用户管理</h1>
    <table>
        <th>
            <td>id</td>
            <td>账号</td>
            <td>昵称</td>
            <td>角色</td>
            <td>创建时间</td>
        </th>
        <c:forEach items="${requestScope.list}" var="entity">
            <tr>
                <td>${entity.id}</td>
                <td>${entity.username}</td>
                <td>${entity.nickname}</td>
                <td>${entity.rolename}</td>
                <td>${entity.createTime}</td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

然后对应效果如下:

在这里插入图片描述

sys/user/list.jsp整合到系统的main.jsp页面中。也就是在我们整体的菜单中点击用户管理需要展示list.jsp中的数据。
在这里插入图片描述

然后我们需要在sys/user/list.jsp中使用bootstrap的样式要调整数据的展示。最终的效果如下:

在这里插入图片描述

2. 添加用户信息

2.1 后端逻辑处理

  我们先实现了后端的用户数据添加的逻辑。Dao的实现

public interface IUserDao {

    /**
     * 查询所有的用户信息
     * @return
     */
    public List<SysUser> list(SysUser user);

    public int save(SysUser user);
}

    @Override
    public int save(SysUser user) {
        QueryRunner queryRunner = MyDbUtils.getQueryRunner();
        String sql = "insert into sys_user(username,password,nickname)values(?,?,?)";
        try {
            return queryRunner.update(sql,user.getUsername(),user.getPassword(),user.getNickname());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return -1;
    }

然后是service的处理

public interface IUserService {

    public List<SysUser> list(SysUser user);

    public int save(SysUser user);
}

public class UserServiceImpl implements IUserService {

    private IUserDao dao = new UserDaoImpl();

    @Override
    public List<SysUser> list(SysUser user) {
        return dao.list(user);
    }

    @Override
    public int save(SysUser user) {
        return dao.save(user);
    }
}

然后是Servlet的处理,在Servlet中需要处理的请求很多。所以我们通过action来识别

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 让客户端提交一个具体请求的标识符
    String action = req.getParameter("action");
    if("save".equals(action)){
        // 表示添加数据
        // 获取客户端提交的数据
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String nickname = req.getParameter("nickname");
        SysUser user = new SysUser();
        user.setUsername(username);
        user.setPassword(password);
        user.setNickname(nickname);
        // 调用Service的方法完成数据的存储
        service.save(user);
        System.out.println(user);
    }else{
        // 表示查询数据
        // 查询所有的用户数据
        List<SysUser> list = service.list(null);
        // 把查询的用户数据存储在 request作用域中
        req.setAttribute("list",list);
        // 通过服务端转发的方式跳转页面
        req.getRequestDispatcher("/sys/user/list.jsp").forward(req,resp);
    }

}

2.2 前端表单处理

  然后我们就需要来处理下前端页面的表单逻辑。首先是点击添加按钮需要跳转到添加数据的页面。

在这里插入图片描述

然后我们在userServlet中需要添加跳转的逻辑处理

在这里插入图片描述

然后添加addOrUpdate.jsp页面。在页面中添加数据的表单信息。具体如下:

在这里插入图片描述

数据提交的地址为/sys/userServlet?action=saveOrUpdate提交成功后数据出现乱码问题。
在这里插入图片描述

2.3 中文乱码

  针对于客户端提交数据到服务器涉及到中文的情况下。因为编码不一致的情况会出现中文乱码问题。我们对应的解决方案如下:

在这里插入图片描述

针对上面表单提交数据乱码的问题。

在这里插入图片描述

我们再添加数据的时候就没有出现乱码了
在这里插入图片描述

为了对编码方式的统一处理。我们添加过滤器。然后在过滤器中统一做编码问题的解决

/**
 * 统一设置Post方式提交数据的编码方式
 */
@WebFilter(filterName = "encodingFiler",urlPatterns = "/*")
public class EncodingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        req.setCharacterEncoding("utf-8"); // 设置编码方式为utf-8
        chain.doFilter(req,res); // 放过请求
    }

}

2.4 头像功能

  在系统用户中我们需要维护用户的头像信息。这块需要涉及到文件上传和下载的操作。这块也是非常基础的功能。正好在这块介绍下在当前项目中的应用。在Servlet中我们需要操作文件上传下载功能我们需要引入相关的依赖commons-fileuploadcommons-io具体如下:

<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.3.3</version>
</dependency>
<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.6</version>
</dependency>

然后具体的上传我们通过百度插件来实现这块的功能。http://fex.baidu.com/webuploader/

<!-- 百度WebUpload插件 -->
<link rel="stylesheet" type="text/css" href="/css/plugins/webuploader/webuploader.css">
<link rel="stylesheet" type="text/css" href="/css/demo/webuploader-demo.css">

<!-- Web Uploader -->
<script type="text/javascript">
    // 添加全局站点信息
    var BASE_URL = '/js/plugins/webuploader';
</script>
<script src="/js/plugins/webuploader/webuploader.min.js"></script>

<script src="/js/demo/webuploader-demo.js"></script>

然后在表单中添加头像的表单域信息

在这里插入图片描述

然后添加对应的js处理逻辑

<script>
    function resetPage(){
        window.location.href="/sys/userServlet"
    }
    $(document).ready(function () { })

    var $ = jQuery,
    $list = $('#thelist'),
    $btn = $('#ctlBtn'),
    state = 'pending',
    uploader;

    // 初始化Web Uploader
    var uploader = WebUploader.create({
        // 选完文件后,是否自动上传。
        auto: true,
        // swf文件路径
        swf: BASE_URL + '/js/Uploader.swf',
        // 文件接收服务端。
        server: '/sys/uploadServlet',
        // 选择文件的按钮。可选。
        // 内部根据当前运行是创建,可能是input元素,也可能是flash.
        pick: '#filePicker',
        // 只允许选择图片文件。
        accept: {
            title: 'Images',
            extensions: 'gif,jpg,jpeg,bmp,png',
            mimeTypes: 'image/*'
        }
    });
    
    uploader.on( 'uploadSuccess', function( file,data ) {
        $( '#'+file.id ).addClass('upload-state-done');
        $("#imgName").val(data._raw);
        $("#msg").text(data._raw)
    });

</script>

有了这些操作后我们就可以看下访问的效果

在这里插入图片描述

在改插件中我们指定的上传地址是/sys/uploadServlet,然后我们需要创建该Servlet。

/**
 * 处理文件上传操作的Servlet
 */
@WebServlet(name = "uploadServlet",urlPatterns = {"/sys/uploadServlet"})
public class UploadServlet extends HttpServlet {

    public static final String UPLOAD_DIRECTORY = "d:\\shopFile\\";
    //private static final String UPLOAD_DIRECTORY = "upload";

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 检测是否是文件
        // 这个路径相对当前应用的目录
        String uploadPath = UPLOAD_DIRECTORY;
        // 如果目录不存在则创建
        File uploadDir = new File(UPLOAD_DIRECTORY);
        if (!uploadDir.exists()) {
            uploadDir.mkdir();
        }
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        try {
            List<FileItem> fileItems = upload.parseRequest(request);
            if(fileItems != null && fileItems.size() > 0){
                for (FileItem fileItem : fileItems) {
                    if(!fileItem.isFormField()){
                        // 表明该信息上传的文件信息
                        String fileName = new File(fileItem.getName()).getName();
                        // 上传的文件名称
                        fileName = new Date().getTime() + fileName.substring(fileName.lastIndexOf("."));

                        String filePath = uploadPath + File.separator + fileName;
                        File storeFile = new File(filePath);
                        // 在控制台输出文件的上传路径
                        System.out.println(filePath);
                        // 保存文件到硬盘
                        fileItem.write(storeFile);
                        //request.setAttribute("message", "文件上传成功!");
                        // 把生成的文件名称返回给客户端
                        PrintWriter writer = response.getWriter();
                        writer.write(fileName);
                        writer.flush();
                        writer.close();
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

文件上传成功后。Servlet中会返回上传成功的文件的名称。我们会把这个名称绑定在表单中的一个隐藏属性中。这样在表单提交的时候会把名称存储在数据库中。

在这里插入图片描述

在这里插入图片描述

同时我们需要修改下保存用户数据和查询数据的逻辑。添加img字段的处理
在这里插入图片描述

Dao中的处理调整

在这里插入图片描述

上传成功后提交表单我们就会在数据库中存储图片名称

在这里插入图片描述

最后在展示用户信息的时候同时展示用户的头像信息。这时我们需要增加一个下载文件的Servlet

@WebServlet(name = "downloadServet",urlPatterns = {"/sys/download"})
public class DownloadServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 下载文件
        String basePath = UploadServlet.UPLOAD_DIRECTORY;
        // 获取需要下载的文件的名称
        String fileName = req.getParameter("fileName");
        // 处理文件上传
        InputStream in = new FileInputStream(basePath+"/"+fileName);
        int size = in.available();
        byte data[] = new byte[size];
        in.read(data);
        in.close();
        // 判断是否是图片
        if(fileName.contains(".jpg")||fileName.contains(".png")){
            resp.setContentType("image/jpg");
        }else{
            resp.setCharacterEncoding("utf-8");
            resp.setContentType("multipart/form-data");
            resp.setHeader("Content-Disposition", "attachment;filename="+  fileName +";filename*=utf-8''"+ URLEncoder.encode(fileName,"UTF-8"));
        }
        ServletOutputStream outputStream = resp.getOutputStream();
        outputStream.write(data);
        outputStream.flush();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

然后在list.jsp中做相关的调整

在这里插入图片描述

展示效果:

在这里插入图片描述

3. BaseServlet

  随着我们的业务需求越来越多那么在Servlet中我们需要通过if语句来判断各种请求。那么会造成doGet或者doPost方法中的代码越来越复杂。不便于开发。这时我们虽然可以通过方法抽取的方式来简化

在这里插入图片描述

  虽然简化了doPost方法。但是还是需要很多的if语句来判断。这时我们可以再进一步的优化,也就是我们约定浏览器提交的请求中携带的action参数即使对应的Servlet中要处理这个请求的方法的名称。这样我们就可以通过反射方式来替换掉上面的if语句处理的情况。彻底分离出各个处理请求的业务方法。

/**
 * 系统公共的Servlet
 * 我们约定客户端提交的请求中的action就是在Servlet要处理的方法的名称
 */
public abstract class BaseServlet extends HttpServlet {

    /**
     * 通过反射的方式调用对象中的Action对应的方法
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取传递的Action参数
        String action = req.getParameter("action");
        try {
            // 获取当前对象对应的Method对象
            Method method = this.getClass()
                    .getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
            // 调用方法
            method.invoke(this,req,resp);
        } catch (Exception e) {
            // 调用方法执行的时候出现了异常
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    // 定义增删改查的基础方法
    public abstract void list(HttpServletRequest req, HttpServletResponse resp) throws Exception;
    public abstract void saveOrUpdate(HttpServletRequest req, HttpServletResponse resp) throws Exception;
    public abstract void remove(HttpServletRequest req, HttpServletResponse resp) throws Exception;
    public abstract void findById(HttpServletRequest req, HttpServletResponse resp) throws Exception;
    public abstract void saveOrUpdatePage(HttpServletRequest req, HttpServletResponse resp) throws Exception;
}

  同时在BaseServlet中又做了抽象的处理。声明了增删改查常用的相关的方法。这样在具体的Servlet中我们就不用繁琐的自己去创建相关的基础放了。然后就可以改造UserServlet中的处理。

@WebServlet(name = "userServlet",urlPatterns = {"/sys/userServlet"})
public class UserServlet extends BaseServlet{

    private IUserService service = new UserServiceImpl();

    @Override
    public void list(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // 表示查询数据
        // 查询所有的用户数据
        List<SysUser> list = service.list(null);
        // 把查询的用户数据存储在 request作用域中
        req.setAttribute("list",list);
        // 通过服务端转发的方式跳转页面
        req.getRequestDispatcher("/sys/user/list.jsp").forward(req, resp);
    }

    @Override
    public void saveOrUpdate(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // 表示添加数据
        // 获取客户端提交的数据
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String nickname = req.getParameter("nickname");
        String img = req.getParameter("img");
        SysUser user = new SysUser();
        user.setUsername(username);
        user.setPassword(password);
        user.setNickname(nickname);
        user.setImg(img);
        // 调用Service的方法完成数据的存储
        service.save(user);
        // 要做数据的查询
        resp.sendRedirect("/sys/userServlet?action="+ Constant.BASE_ACTION_LIST);
    }

    @Override
    public void remove(HttpServletRequest req, HttpServletResponse resp) throws Exception {

    }

    @Override
    public void findById(HttpServletRequest req, HttpServletResponse resp) throws Exception {

    }

    @Override
    public void saveOrUpdatePage(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        req.getRequestDispatcher("/sys/user/addOrUpdate.jsp").forward(req,resp);
    }
}

最后注意调整之前用户操作的相关请求的地址中的action参数即可。

4. 更新用户信息

  用户信息的更新操作,实现的逻辑是

  1. 点击更新按钮,传递用户编号到后端
  2. 后端服务获取到id查询出对应的用户数据
  3. 跳转到更新页面。回写数据到表单中
  4. 提交更新的数据到服务
  5. 服务器获取到更新的数据后更新到数据库中

点击更新按钮传递编号到后端服务的实现

在这里插入图片描述

在这里插入图片描述

然后后端处理逻辑,Dao增加根据id查询的方法

@Override
public SysUser findById(int id) {
    QueryRunner queryRunner = MyDbUtils.getQueryRunner();
    String sql = "select * from sys_user where id = ?";
    try {
        // BeanListHandler 会自动的帮助我们完成字段和属性的映射。前提是属性和字段完全一直
        // 此处不会通过驼峰命名法 装换
        SysUser user = queryRunner.query(sql, new ResultSetHandler<SysUser>() {
            @Override
            public SysUser handle(ResultSet resultSet) throws SQLException {
                // 存储返回结果的容器
                if(resultSet.next()){
                    // 每次循环一次 user 存储一条数据
                    SysUser user = new SysUser();
                    user.setId(resultSet.getInt("id"));
                    user.setUsername(resultSet.getString("username"));
                    user.setPassword(resultSet.getString("password"));
                    user.setNickname(resultSet.getString("nickname"));
                    user.setRoleId(resultSet.getInt("role_id"));
                    user.setRolename(resultSet.getString("rolename"));
                    user.setImg(resultSet.getString("img"));
                    user.setCreateTime(resultSet.getDate("create_time"));
                    return user;
                }
                return null; // 返回查询的结果
            }
        },id);
        return user;
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }
    return null;
}

service同样需要添加对应的方法,然后就是Servlet中的处理

@Override
public void saveOrUpdatePage(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    // 需要获取客户端提交的id信息
    String id = req.getParameter("id");
    if(StringUtils.isNotEmpty(id)){
        // 说明是更新操作。那么我们需要根据id查询出用户的具体的信息
        SysUser user = service.findById(Integer.parseInt(id));
        req.setAttribute("entity",user);
    }
    req.getRequestDispatcher("/sys/user/addOrUpdate.jsp").forward(req,resp);
}

然后在表单中回写数据

<form class="form-horizontal m-t" id="signupForm" action="/sys/userServlet?action=saveOrUpdate" method="post">
    <input type="hidden" name="id" value="${entity.id}">
    <div class="form-group">
        <label class="col-sm-3 control-label">用户名:</label>
        <div class="col-sm-8">
            <input id="username" name="username" class="form-control" value="${entity.username}"
                   type="text" aria-required="true" aria-invalid="true" class="error">
        </div>
    </div>
    <div class="form-group">
        <label class="col-sm-3 control-label">昵称:</label>
        <div class="col-sm-8">
            <input id="lastname" name="nickname" class="form-control" value="${entity.nickname}"
                   type="text" aria-required="true" aria-invalid="false" class="valid">
        </div>
    </div>

    <div class="form-group">
        <label class="col-sm-3 control-label">密码:</label>
        <div class="col-sm-8">
            <input id="password" name="password" class="form-control" value="${entity.password}" type="password">
        </div>
    </div>
    <div class="form-group">
        <label class="col-sm-3 control-label">确认密码:</label>
        <div class="col-sm-8">
            <input id="confirm_password" name="confirm_password" value="${entity.password}"
                   class="form-control" type="password">
            <span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 请再次输入您的密码</span>
        </div>
    </div>
    <<div class="form-group">
        <input type="hidden" name="img" value="${entity.img}" id="imgName">
        <label class="col-sm-3 control-label">头像:</label>
        <div class="col-sm-8">
            <!--dom结构部分-->
            <div id="uploader-demo">
                <!--用来存放item-->
                <div id="fileList" class="uploader-list">${entity.img}</div>
                <div id="filePicker">选择图片</div>
            </div>
        </div>
    </div>

    <div class="form-group">
        <div class="col-sm-8 col-sm-offset-3">
            <button class="btn btn-default" onclick="resetPage()" type="button">取消</button>
            <button class="btn btn-primary" type="submit">提交</button>
        </div>
    </div>
</form>

需要注意的是更新数据需要在表单中携带id,我们在上面通过<input type="hidden" name="id" value="${entity.id}">实现。提交后。我们对应的要修改Servlet中的逻辑

在这里插入图片描述

最后需要添加Dao中的更新方法

@Override
public int updateById(SysUser user) {
    QueryRunner queryRunner = MyDbUtils.getQueryRunner();
    String sql = "update sys_user set username=?,password=?,nickname=?,img=? where id =?";
    try {
        return queryRunner.update(sql,user.getUsername()
                ,user.getPassword(),user.getNickname(),user.getImg(),user.getId());
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }
    return -1;
}

注意:更新功能没有问题。我们还需要检查下添加功能是否受到影响!!!

5. 删除用户信息

  删除功能是一个非常基础的功能。在点击删除按钮的时候,我们需要给出提示框,防止用户误操作,这块的提示框我们通过SweetAlert来实现。效果如下:

在这里插入图片描述

引入sweetAlert组件需要添加相关的css和js文件

<!-- Sweet Alert -->
<link href="/css/plugins/sweetalert/sweetalert.css" rel="stylesheet">
<!-- Sweet alert -->
<script src="/js/plugins/sweetalert/sweetalert.min.js"></script>

添加对应的点击事件的处理方法

function removeData(id){
    swal({
        title: "您确定要删除这条信息吗",
        text: "删除后将无法恢复,请谨慎操作!",
        type: "warning",
        showCancelButton: true,
        confirmButtonColor: "#DD6B55",
        confirmButtonText: "删除",
        closeOnConfirm: false
    }, function () {
        swal("删除成功!", "您已经永久删除了这条信息。", "success");
        $.get("/sys/userServlet?action=remove&id="+id,function(msg){
            // 再发起一个查询的操作
            window.location.href="/sys/userServlet?action=list"
        })
    });
}

然后就是对应的后端处理逻辑

@Override
public void remove(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    String id = req.getParameter("id");
    int count = service.deleteById(Integer.parseInt(id));
    PrintWriter writer = resp.getWriter();
    writer.write(count+"");
    writer.flush();
    writer.close();
}

6. 分页查询

6.1 分页介绍

  分页功能在数据展示中是非常有必要的。原因有两块:

  1. 展示太多的数据用户是否能够消化
  2. 太多的数据我们的浏览器是否能够支持(服务器是否能够支持)

在这里插入图片描述

6.2 后端分页处理

  我们在后端处理查询操作的时候需要做分页的处理。我们首先需要了解MySQL中的分页语句。需要使用到limit 关键字

# 分页的SQL实现-结合不同的数据库来实现
SELECT * FROM sys_user  LIMIT 0,3  # limit 开始的位置,取几条记录

  搞清楚了分页SQL的实现那么就可以处理Dao中分页逻辑的实现了,先在Dao接口中声明分页的方法

/**
 * Dao层的接口对象
 */
public interface IUserDao {

    /**
     * 查询所有的用户信息
     * @return
     */
    public List<SysUser> list(SysUser user);

    /**
     * 分页查询的方法
     * @param user 查询条件
     * @param pageNum 页码
     * @param pageSize 每页显示的条数
     * @return
     */
    public List<SysUser> listPage(SysUser user,int pageNum,int pageSize);

    public int save(SysUser user);

    public SysUser findById(int id);

    public int updateById(SysUser user);

    int deleteById(int id);
}

然后就是对应的分页查询实现

@Override
public List<SysUser> listPage(SysUser user, int pageNum, int pageSize) {
    QueryRunner queryRunner = MyDbUtils.getQueryRunner();
    String sql = "select * from sys_user limit ?,?";
    // 计算 分页开始的位置
    int startNo = (pageNum-1) * pageSize;
    try {
        // BeanListHandler 会自动的帮助我们完成字段和属性的映射。前提是属性和字段完全一直
        // 此处不会通过驼峰命名法 装换
        // List<SysUser> list = queryRunner.query(sql, new BeanListHandler<SysUser>(SysUser.class));
        List<SysUser> list = queryRunner.query(sql, new ResultSetHandler<List<SysUser>>() {
            @Override
            public List<SysUser> handle(ResultSet resultSet) throws SQLException {
                // 存储返回结果的容器
                List<SysUser> list = new ArrayList<>();
                while(resultSet.next()){
                    // 每次循环一次 user 存储一条数据
                    SysUser user = new SysUser();
                    user.setId(resultSet.getInt("id"));
                    user.setUsername(resultSet.getString("username"));
                    user.setPassword(resultSet.getString("password"));
                    user.setNickname(resultSet.getString("nickname"));
                    user.setRoleId(resultSet.getInt("role_id"));
                    user.setRolename(resultSet.getString("rolename"));
                    user.setImg(resultSet.getString("img"));
                    user.setCreateTime(resultSet.getDate("create_time"));
                    list.add(user); // 把查询的记录封装到了集合容器中
                }
                return list; // 返回查询的结果
            }
        },startNo,pageSize);
        return list;
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }
    return null;
}

可以通过main方法来测试

在这里插入图片描述

然后针对分页操作的相关数据比较多,我们可以自定义一个PageUtils来封装分页相关的信息。具体如下:

/**
 * 分页的工具类
 */
public class PageUtils {

    private String key; // 查询的关键字

    private int pageSize = 5; // 每页显示的条数

    private int pageNum = 1; // 第几页  默认查询第一页

    private int totalCount ; // 总的记录数

    private int totalPage ; // 总的页数

    private List<?> list ; // 分页显示的数据

    /**
     * SQL分页中的 开始的位置
     * @return
     */
    public int getStart(){
        return (this.getPageNum() - 1) * this.getPageSize();
    }

    public int getEnd(){
        return this.getPageNum() * this.getPageSize();
    }


    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public int getPageNum() {
        return pageNum;
    }

    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }

    public int getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
    }

    public int getTotalPage() {
        return totalPage;
    }

    public void setTotalPage(int totalPage) {
        this.totalPage = totalPage;
    }

    public List<?> getList() {
        return list;
    }

    public void setList(List<?> list) {
        this.list = list;
    }
}

然后在Servlet和Service中实现分页处理。同时调整Dao中的实现代码

@Override
public void list(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    // 查询分页相关的参数
    String ps = req.getParameter("pageSize");
    String pn = req.getParameter("pageNum");
    // 声明默认的分页参数
    int pageSize = 5; // 默认每页显示5条
    int pageNum = 1; // 默认第一页
    if(StringUtils.isNotEmpty(ps)){
        pageSize = Integer.parseInt(ps);
    }

    if(StringUtils.isNotEmpty(pn)){
        pageNum = Integer.parseInt(pn);
    }
    PageUtils pageUtils = new PageUtils();
    pageUtils.setPageNum(pageNum);
    pageUtils.setPageSize(pageSize);

    // 做分页的查询
    service.listPage(pageUtils);

    // 表示查询数据
    // 查询所有的用户数据
    //List<SysUser> list = service.list(null);
    // 把查询的用户数据存储在 request作用域中
    req.setAttribute("pageUtils",pageUtils);
    // 通过服务端转发的方式跳转页面
    req.getRequestDispatcher("/sys/user/list.jsp").forward(req, resp);
}

然后对应的Service中既有查询分页数据也得查询总的记录数

@Override
public void listPage(PageUtils pageUtils) {
    List<SysUser> list = dao.listPage(pageUtils);
    int count = dao.count();
    // 封装分页的数据
    pageUtils.setList(list);
    pageUtils.setTotalCount(count);

}

然后在Dao中提供这两个数据操作的支持

@Override
public List<SysUser> listPage(PageUtils pageUtils) {
    QueryRunner queryRunner = MyDbUtils.getQueryRunner();
    String sql = "select * from sys_user limit ?,?";
    // 计算 分页开始的位置
    int startNo = pageUtils.getStart();
    try {
        // BeanListHandler 会自动的帮助我们完成字段和属性的映射。前提是属性和字段完全一直
        // 此处不会通过驼峰命名法 装换
        // List<SysUser> list = queryRunner.query(sql, new BeanListHandler<SysUser>(SysUser.class));
        List<SysUser> list = queryRunner.query(sql, new ResultSetHandler<List<SysUser>>() {
            @Override
            public List<SysUser> handle(ResultSet resultSet) throws SQLException {
                // 存储返回结果的容器
                List<SysUser> list = new ArrayList<>();
                while(resultSet.next()){
                    // 每次循环一次 user 存储一条数据
                    SysUser user = new SysUser();
                    user.setId(resultSet.getInt("id"));
                    user.setUsername(resultSet.getString("username"));
                    user.setPassword(resultSet.getString("password"));
                    user.setNickname(resultSet.getString("nickname"));
                    user.setRoleId(resultSet.getInt("role_id"));
                    user.setRolename(resultSet.getString("rolename"));
                    user.setImg(resultSet.getString("img"));
                    user.setCreateTime(resultSet.getDate("create_time"));
                    list.add(user); // 把查询的记录封装到了集合容器中
                }
                return list; // 返回查询的结果
            }
        },startNo,pageUtils.getPageSize());
        return list;
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }
    return null;
}

@Override
    public int count() {
        QueryRunner queryRunner = MyDbUtils.getQueryRunner();
        String sql = "select count(1) from sys_user ";
        try {
            return queryRunner.query(sql, new ResultSetHandler<Integer>() {
                @Override
                public Integer handle(ResultSet resultSet) throws SQLException {
                    resultSet.next();
                    return resultSet.getInt(1);
                }
            });
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return 0;
    }

然后就可以测试分页的数据了:
在这里插入图片描述

针对UserServlet中查询分页信息的方法有很多分页处理的逻辑造成整个方法显得臃肿,不是很简洁针对这种情况我们可以把公共代码提取到BaseServlet中。

/**
 * 系统公共的Servlet
 * 我们约定客户端提交的请求中的action就是在Servlet要处理的方法的名称
 */
public abstract class BaseServlet extends HttpServlet {

    protected PageUtils pageUtils ;


    /**
     * 通过反射的方式调用对象中的Action对应的方法
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取传递的Action参数
        String action = req.getParameter("action");
        try {
            // 获取当前对象对应的Method对象
            Method method = this.getClass()
                    .getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
            // 调用方法
            method.invoke(this,req,resp);
        } catch (Exception e) {
            // 调用方法执行的时候出现了异常
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    // 定义增删改查的基础方法
    public  void list(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        pageUtils = new PageUtils();
        // 查询分页相关的参数
        String ps = req.getParameter("pageSize");
        String pn = req.getParameter("pageNum");
        // 声明默认的分页参数
        int pageSize = 5; // 默认每页显示5条
        int pageNum = 1; // 默认第一页
        if(StringUtils.isNotEmpty(ps)){
            pageSize = Integer.parseInt(ps);
        }

        if(StringUtils.isNotEmpty(pn)){
            pageNum = Integer.parseInt(pn);
        }
        pageUtils.setPageNum(pageNum);
        pageUtils.setPageSize(pageSize);

    };
    public abstract void saveOrUpdate(HttpServletRequest req, HttpServletResponse resp) throws Exception;
    public abstract void remove(HttpServletRequest req, HttpServletResponse resp) throws Exception;
    public abstract void findById(HttpServletRequest req, HttpServletResponse resp) throws Exception;
    public abstract void saveOrUpdatePage(HttpServletRequest req, HttpServletResponse resp) throws Exception;
}

然后在业务代码中只需要调用父类中的方法即可:如下,代码整个就非常直观和简洁了。

@Override
public void list(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    super.list(req,resp); // 调用父类中的方法完成分页数据的处理
    // 做分页的查询
    service.listPage(pageUtils);
    // 把查询的用户数据存储在 request作用域中
    req.setAttribute("pageUtils",pageUtils);
    // 通过服务端转发的方式跳转页面
    req.getRequestDispatcher("/sys/user/list.jsp").forward(req, resp);
}

6.3 前端分页功能

  前端分页展示的效果如下:

在这里插入图片描述

首页是下面的分页条目。这块我们是通过DataTables插件中的分页栏来实现的。

<div class="row">
    <div class="col-sm-6">
        <div class="dataTables_info" id="DataTables_Table_0_info" role="alert" aria-live="polite" aria-relevant="all">显示
            1 到 10 项,共 57 项
        </div>
    </div>
    <div class="col-sm-6">
        <div class="dataTables_paginate paging_simple_numbers" id="DataTables_Table_0_paginate">
            <ul class="pagination">
                <li class="paginate_button previous disabled" aria-controls="DataTables_Table_0" tabindex="0"
                    id="DataTables_Table_0_previous"><a href="#">上一页</a></li>
                <li class="paginate_button active" aria-controls="DataTables_Table_0" tabindex="0"><a href="#">1</a>
                </li>
                <li class="paginate_button " aria-controls="DataTables_Table_0" tabindex="0"><a href="#">2</a></li>
                <li class="paginate_button " aria-controls="DataTables_Table_0" tabindex="0"><a href="#">3</a></li>
                <li class="paginate_button " aria-controls="DataTables_Table_0" tabindex="0"><a href="#">4</a></li>
                <li class="paginate_button " aria-controls="DataTables_Table_0" tabindex="0"><a href="#">5</a></li>
                <li class="paginate_button " aria-controls="DataTables_Table_0" tabindex="0"><a href="#">6</a></li>
                <li class="paginate_button next" aria-controls="DataTables_Table_0" tabindex="0"
                    id="DataTables_Table_0_next"><a href="#">下一页</a></li>
            </ul>
        </div>
    </div>
</div>

然后需要引入DataTabls的样式文件

<!-- Data Tables -->
<link href="/css/plugins/dataTables/dataTables.bootstrap.css" rel="stylesheet">

绑定我们对应的分页的数据

<div class="col-sm-6">
    <div class="dataTables_info" id="DataTables_Table_0_info" role="alert" aria-live="polite"
         aria-relevant="all">显示 ${pageUtils.start +1 } 到 ${pageUtils.end} 项,共 ${pageUtils.totalCount} 项
    </div>
</div>

在这里插入图片描述

然后处理动态分页中的数字页。我们在DBUtils中定义getPageList方法,提供对应的分页数字

public List<String> getPageList(){
    pageList = new ArrayList<>();
    totalPage = getTotalPage();
    // 获取总的页数
    if(totalPage < 7){
        // 总共没有7条记录
        for (int i = 1; i <= totalPage; i++) {
            pageList.add(i+"");
        }
    }else{
        if(pageNum == 1 || pageNum == 2 || pageNum == 3){
            pageList = Arrays.asList("1","2","3","...",totalPage+"");
        }else{
            pageList = Arrays.asList((pageNum-2)+"",(pageNum-1)+"",pageNum+"","..."+totalPage);
        }
    }
    return pageList;
}

对应的还需要实现getTotalPage方法的逻辑

/**
 * 获取总的页数
 * @return
 */
public int getTotalPage() {
    // 总的记录数/每页显示的条数
    totalPage = (int)Math.ceil(((double)totalCount)/pageSize);
    return totalPage;
}

有了这个方法的支持。对应的动态分页数字就可以实现了

<div class="col-sm-6">
    <div class="dataTables_paginate paging_simple_numbers" id="DataTables_Table_0_paginate">
        <ul class="pagination">
            <li class="paginate_button previous disabled" aria-controls="DataTables_Table_0"
                tabindex="0" id="DataTables_Table_0_previous">
                <a href="javascript:void(0)" onclick="goPre()">上一页</a>
            </li>
            <c:forEach items="${requestScope.pageUtils.pageList}" var="page">
                <c:if test="${page == pageUtils.pageNum}">
                    <li class="paginate_button active" aria-controls="DataTables_Table_0" tabindex="0">
                        <a href="javascript:void(0)" onclick="goPage(${page})">${page}</a></li>
                </c:if>
                <c:if test="${page != pageUtils.pageNum}">
                    <li class="paginate_button " aria-controls="DataTables_Table_0" tabindex="0">
                        <a href="javascript:void(0)" onclick="goPage(${page})">${page}</a></li>
                </c:if>
            </c:forEach>
            <c:if test="${pageUtils.pageNum == pageUtils.totalPage}">
                <li class="paginate_button next disabled" aria-controls="DataTables_Table_0" tabindex="0"
                    id="DataTables_Table_0_next">
                    <a href="#">下一页</a>
                </li>
            </c:if>
            <c:if test="${pageUtils.pageNum != pageUtils.totalPage}">
                <li class="paginate_button next" aria-controls="DataTables_Table_0" tabindex="0"
                    id="DataTables_Table_0_next">
                    <a href="javascript:void(0)" onclick="goNext()">下一页</a>
                </li>
            </c:if>
        </ul>
    </div>
</div>

要实现具体的分页功能。我们还需要提供提交数据的表单。这块和关键字查询关联起来

<form action="/sys/userServlet" id="myForm" method="get">
    <div class="input-group">
        <input type="text" placeholder="请输入关键词" class="input-sm form-control">
        <span class="input-group-btn">
                <button type="button" class="btn btn-sm btn-primary"> 搜索</button>
        </span>
        <input type="hidden" name="action" value="list">
        <input type="hidden" id="pageNum" name="pageNum" value="${pageUtils.pageNum}">
        <input type="hidden" id="pageSize" name="pageSize" value="${pageUtils.pageSize}">
    </div>
</form>

最后就是提供相关的js方法

// 分页相关的方法
function goPre(){
    // 前一页:当前页-1
    $("#pageNum").val(${pageUtils.pageNum - 1});
    // 提交表单
    $("#myForm").submit();
}

function goPage(page){
    // 前一页:当前页-1
    $("#pageNum").val(page);
    // 提交表单
    $("#myForm").submit();
}

function goNext(){
    // 下一页:当前页+1
    $("#pageNum").val(${pageUtils.pageNum + 1});
    // 提交表单
    $("#myForm").submit();
}

搞定分页功能,当然我们还需要添加关键字查询的逻辑

7. 带条件查询

  在一个基础功能模块中。带条件查询的功能也是非常用必要的。而已是需要结合在分页功能中的。在用户管理中我们也需要来实现这块的功能。在PageUtils中定义看一个key的属性。那么在查询的表单中我们添加一个key的表单域。

<div class="col-sm-3">
    <form action="/sys/userServlet" id="myForm" method="get">
        <div class="input-group">
            <input type="text" name="key" value="${pageUtils.key}" placeholder="请输入关键词" class="input-sm form-control">
            <span class="input-group-btn">
                    <button type="submit" class="btn btn-sm btn-primary"> 搜索</button>
            </span>
            <input type="hidden" name="action" value="list">
            <input type="hidden" id="pageNum" name="pageNum" value="${pageUtils.pageNum}">
            <input type="hidden" id="pageSize" name="pageSize" value="${pageUtils.pageSize}">
        </div>
    </form>
</div>

  然后修改PageUtils中的逻辑。添加对key的处理。注意这块我们只需要在BaseServlet中处理即可

// 定义增删改查的基础方法
public  void list(HttpServletRequest req, HttpServletResponse resp) throws Exception{
    pageUtils = new PageUtils();
    // 查询分页相关的参数
    String ps = req.getParameter("pageSize");
    String pn = req.getParameter("pageNum");
    String key = req.getParameter("key");
    // 声明默认的分页参数
    int pageSize = 5; // 默认每页显示5条
    int pageNum = 1; // 默认第一页
    if(StringUtils.isNotEmpty(ps)){
        pageSize = Integer.parseInt(ps);
    }

    if(StringUtils.isNotEmpty(pn)){
        pageNum = Integer.parseInt(pn);
    }

    if(StringUtils.isNotEmpty(key)){
        // 如果查询条件不为空。那么设置当前页为1
        pageNum = 1;
    }

    pageUtils.setPageNum(pageNum);
    pageUtils.setPageSize(pageSize);
    pageUtils.setKey(key);

};

最后在UserDaoImpl中添加带条件的查询操作即可

String sql = "select * from sys_user limit ?,?";
if(StringUtils.isNotEmpty(pageUtils.getKey())){
    // 需要带条件查询
    sql = "select * from sys_user where username like '%"+pageUtils.getKey()+"%' or nickname like '%"+pageUtils.getKey()+"%'  limit ?,?";
}

具体效果如下:
在这里插入图片描述

把分页的公共部分可以提取出来。在更目录下创建一个common/commonPage.jsp页面

<%--
  weChat:boge_java
  QQ:279583842
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div class="row">
    <div class="col-sm-6">
        <div class="dataTables_info" id="DataTables_Table_0_info" role="alert" aria-live="polite"
             aria-relevant="all">显示 ${pageUtils.start +1 } 到 ${pageUtils.end} 项,共 ${pageUtils.totalCount} 项
        </div>
    </div>
    <div class="col-sm-6">
        <div class="dataTables_paginate paging_simple_numbers" id="DataTables_Table_0_paginate">
            <ul class="pagination">
                <li class="paginate_button previous disabled" aria-controls="DataTables_Table_0"
                    tabindex="0" id="DataTables_Table_0_previous">
                    <a href="javascript:void(0)" οnclick="goPre()">上一页</a>
                </li>
                <c:forEach items="${requestScope.pageUtils.pageList}" var="page">
                    <c:if test="${page == pageUtils.pageNum}">
                        <li class="paginate_button active" aria-controls="DataTables_Table_0" tabindex="0">
                            <a href="javascript:void(0)" οnclick="goPage(${page})">${page}</a></li>
                    </c:if>
                    <c:if test="${page != pageUtils.pageNum}">
                        <li class="paginate_button " aria-controls="DataTables_Table_0" tabindex="0">
                            <a href="javascript:void(0)" οnclick="goPage(${page})">${page}</a></li>
                    </c:if>
                </c:forEach>
                <c:if test="${pageUtils.pageNum == pageUtils.totalPage}">
                    <li class="paginate_button next disabled" aria-controls="DataTables_Table_0" tabindex="0"
                        id="DataTables_Table_0_next">
                        <a href="#">下一页</a>
                    </li>
                </c:if>
                <c:if test="${pageUtils.pageNum != pageUtils.totalPage}">
                    <li class="paginate_button next" aria-controls="DataTables_Table_0" tabindex="0"
                        id="DataTables_Table_0_next">
                        <a href="javascript:void(0)" οnclick="goNext()">下一页</a>
                    </li>
                </c:if>
            </ul>
        </div>
    </div>
</div>

然后在/sys/user/list.jsp中通过include指令来引入这个分页的公共页面

<%@include file="/common/commonPage.jsp"%>

这样就能达到简化和减少冗余代码的效果。

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

波波烤鸭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值