------web 开发登录功能------

  修改login.html文件:注意加粗部分为 msg 字符串不为空时候 才进行显示

<!DOCTYPE html>
<!-- saved from url=(0050)http://getbootstrap.com/docs/4.0/examples/sign-in/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="http://getbootstrap.com/favicon.ico"> <title>Signin Template for Bootstrap</title> <!-- Bootstrap core CSS -->
<link href="/asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet"> <!-- Custom styles for this template -->
<link href="/asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
</head> <body class="text-center">
<form class="form-signin" th:action="@{/user/login}" method="post">
<img class="mb-4" src="/asserts/img/bootstrap-solid.svg" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<p style="color: red;" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<label for="username" class="sr-only" th:text="#{login.username}">Email address</label>
<input name="username" type="email" id="username" th:placeholder="#{login.username}" class="form-control" placeholder="Email address" required="" autofocus="">
<label for="inputPassword" th:text="#{login.password}" class="sr-only">Password</label>
<input name="password" type="password" th:placeholder="#{login.password}" id="inputPassword" class="form-control" placeholder="Password" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"/> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
</form>
</body></html>

  添加一个controller 对应页面跳转

package com.lixuchun.springboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import java.util.Map; @Controller
public class LoginController { // @RequestMapping(value = "/user/login", method = RequestMethod.POST)
@PostMapping(value = "/user/login")
public String login(@RequestParam String username, @RequestParam String password
,Map<String, Object> map) {
if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
// 登录成功
return "dashboard";
} else {
map.put("msg", "用户登录失败!");
// 登录失败
return "login";
}
}
}

  实现效果:填写正确情况下跳转到 dashboard.html页面中,错误情况下 返回login页面 并且显示 用户登录失败!

  登录 开发期间模板引擎页面修改以后要实时生效

    禁用模板引擎缓存:spring.thymeleaf.cache=false

    修改页面完成以后 ctrl + f9 重新编译

  当页面位于 dashboard 页面 按 F5 会有是否表单再次提交提示 (表单重复提交)

    可以进行重定向防止重复提交

     if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
// 登录成功
// 防止表单从夫提交 可以从定向导主页
return "redirect:/main.html";
} else {
map.put("msg", "用户登录失败!");
// 登录失败
return "login";
}

    修改Mvc View视图解析器

    @Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}
};
return adapter;
}

  如果这样修改了 直接访问 localhost:8080/main.html 就可以直接访问 登录功能没起到作用 那么引入拦截器

  拦截器 编写拦截器controller 必须实现 HandlerInterceptor

  pre 在登录之前进行检查:

package com.lixuchun.springboot.component;

import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; /**
* 进行登录检查
*/
public class LoginHandlerIntercepter implements HandlerInterceptor{ // 目标方法预检查
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("loginUser");
if (user == null) {
// 未登录 返回登录页面
request.setAttribute("msg", "没有用户权限 请先登录");
       // 没有登录跳转到 /index.html 页面
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
} else {
// 已经登录 放行请求
return true;
}
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }
}

  Mvc 配置文件添加 Interceptor 组件 进行注册:

    @Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
} // 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截任意请求
// 已经做好了静态资源的映射 *.css *.js 访问等 不需要做处理
registry.addInterceptor(new LoginHandlerIntercepter()).addPathPatterns("/**")
.excludePathPatterns("/index.html", "/", "/user/login");
}
};
return adapter;
}

  访问效果:在没有登录的情况下 访问 localhost:8080/main.html

  登录后再次访问:左侧上面 test@test.com 为登录名称 dashboard 页面 [[${session.loginUser}]] 行内表达式取值

  CRUD-员工列表

  实验要求:

    1) RestfulCRUD: CRUD 满足Rest风格

    URI : /资源名称/资源标识  HTTP请求方式区分对资源的CRUD操作

       /emp/{id}

    

    2). 实验的请求架构

    

    

    3).员工列表

      抽取公共元素片段

        <div th:fragment="copy">&COPY 2011 thymes Virtual Grocery</div>

      引入公共元素片段

        <div th:insert="~{footer::copy}"></div>

        ~{templatename::selector} :模板名 :: 选择器

        ~{templatename::fragmentname}:模板名::片段名

      三种引入公共片段th属性:

        th:insert :将公共的片段整个插入到声明引用的div中

        th:replace: 将声明引入的元素替换为公共片段

        th:include:被引入的片段内容包含近这个标签中

        如果使用th:insert 等属性进行引入 可以不用写 ~{}

        行内写法加上 [[~{}]] , [(~{})] (转义不转义)

      dashboard 页面定义公共片段头 th:fragment="topbar"

<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
</li>
</ul>
</nav>

      list 页面头取法

<!-- 引入抽取的topbar -->
<!-- 模板名称:使用thymeleaf的配置规则进行解析 -->
<div th:replace="~{dashboard::topbar}"></div>

      dashboard 页面定义公共片段侧边栏 id="sideBar"

    <nav id="sideBar" class="col-md-2 d-none d-md-block bg-light sidebar" style="margin-top: 50px;">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
Dashboard <span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
Orders
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart"><circle cx="9" cy="21" r="1"></circle><circle cx="20" cy="21" r="1"></circle><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path></svg>
Products
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" th:href="@{/emps}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
员工管理
</a>
</li>
</ul>
</div>
</nav>

      list 页面侧边栏取法

<!-- 引入侧边栏 -->
<div th:replace="~{dashboard::#sideBar}"></div>

    也可以单独提出一个bar文件 然后在各个页面进行调用

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- top bar -->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
</li>
</ul>
</nav> <!-- side bar -->
<nav id="sideBar" class="col-md-2 d-none d-md-block bg-light sidebar" style="margin-top: 50px;">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart"><circle cx="9" cy="21" r="1"></circle><circle cx="20" cy="21" r="1"></circle><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path></svg>
Products
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" th:href="@{/emps}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
员工管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layers"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
Integrations
</a>
</li>
</ul>
</div>
</nav>
</body>
</html>

    页面进行调用 list 页面 效果和之前的效果一样

<!-- 引入抽取的topbar -->
<!-- 模板名称:使用thymeleaf的配置规则进行解析 -->
<div th:replace="commons/bar::topbar"></div>
<!-- 引入侧边栏 -->
<div th:replace="commons/bar::#sideBar"></div>

    

  引入片段也可以传入参数:

    在 dashboard.html 页面引入左侧栏时候 传入参数

<div th:replace="commons/bar::#sideBar(activeUri='main.html')"></div>

    在 bar.html 页面设置左侧栏时候 通过传过来的参数设置 是否高亮

<li class="nav-item">
  <a class="nav-link active" href="#"
  th:href="@{/main.html}" th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
  Dashboard <span class="sr-only">(current)</span>
</a>
</li>

   

  员工列表做数据展示:

  list.html 页面编写

    <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
      <div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
        <div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
        <div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div>
        </div>
          <div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
            <div style="position:absolute;width:200%;height:200%;left:0; top:0">
          </div>
        </div>
      </div>
<h2><button class="btn btn-sm btn-success">员工添加</button></h2>
<h2>Section title</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>#</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<th>options</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.id}"></td>
<td>[[${emp.lastName}]]</td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender}==0?'girl':'boy'"></td>
<td th:text="${emp.department.departmentName}"></td>
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"></td>
<td>
<button class="btn btn-sm btn-primary">编辑员工</button>
<button class="btn btn-sm btn-danger">删除员工</button>
</td>
</tr>
</tbody>
</table>
</div>
</main>

  做一个伪处理 employeeDao

package com.lixuchun.springboot.dao;

import com.lixuchun.springboot.entities.Department;
import com.lixuchun.springboot.entities.Employee;
import org.springframework.stereotype.Repository; import java.util.ArrayList;
import java.util.Date;
import java.util.List; @Repository
public class EmployeeDao {
public Employee selectEmpById(Integer id) {
return new Employee();
} public List<Employee> selectEmps() {
List<Employee> empList = new ArrayList<Employee>();
for (int i = 0; i < 6; i++) {
Employee emp = new Employee(i, "emp" + i,
i + "emp@emp.com", 0,
new Department(i, "dep"+ i), new Date());
empList.add(emp);
}
return empList;
}
}

  employee / department

department
private Integer id;
private String departmentName; employee
id
lastName
email
gender
department
birth

  EmpController 编写

    @Autowired
EmployeeDao employeeDao; // 查询所有员工返回列表页面
@GetMapping("/emps")
public String list(Model model) {
List<Employee> employees = employeeDao.selectEmps();
model.addAttribute("emps", employees);
// classpath:/templates/xxx.html
return "emp/list";
}

  最后页面效果:

  员工添加功能:

    list 添加按钮  <a class="btn btn-sm btn-success" href="emp" th:href="@{/emp}">员工添加</a>

    emp get 请求到员工添加页面

    emp post 添加员工

  

  其中部门为后台查出来的,前后台处理

<select class="form-control">
<option th:value="${dept.id}" th:each="dept:${depts}" th:text="dept.departmentName"></option>
</select>

  点击添加按钮 post 到 emp为添加功能

<form ah:action="@{/emp}" method="post">

  员工添加controller

  修改日期格式:spring.mvc.date-format=yyyy-MM-dd

  员工修改:emp/id ------->get 方式

<a class="btn btn-sm btn-primary" th:href="@{/emp/} + ${emp.id}">编辑员工</a>

  页面需要回显:

    th:value="${emp!=null}?${emp.lastName}">

    th:checked="${emp!=null}?${emp.gender==1}" / gender==0

    th:selected="${emp!=null}?${dept.id==emp.department.id}"

  如何发送put请求,到员工修改controller

  删除:delete请求

  出现了按钮换行 因为 form的添加 所以进行修改

  设置按钮del_uri

    button 设置 :th:attr="del_uri=@{emp/} + ${emp.id}"

    form 设置:id="deleteEmpForm"

  最后效果:

spring boot 尚桂谷学习笔记05 ---Web的更多相关文章

  1. spring boot 尚桂谷学习笔记04 ---Web开始

    ------web开发------ 1.创建spring boot 应用 选中我们需要的模块 2.spring boot 已经默认将这些场景配置好了 @EnableAutoConfiguration ...

  2. spring boot 尚桂谷学习笔记10 数据访问02 mybatis

    数据访问 mybatis 创建一个 springboot 工程,模块选择 sql 中 mysql(数据驱动), jdbc(自动配置数据源), mybatis Web模块中选择 web pom 引入: ...

  3. spring boot 尚桂谷学习笔记11 数据访问03 JPA

    整合JPA SpringData 程序数据交互结构图 (springdata jpa 默认使用 hibernate 进行封装) 使用之后就关注于 SpringData 不用再花多经历关注具体各个交互框 ...

  4. spring boot 尚桂谷学习笔记09 数据访问

    springboot 与数据库访问 jdbc, mybatis, spring data jpa,  1.jdbc原生访问 新建项目 使用 springboot 快速构建工具 选中 web 组件 sq ...

  5. spring boot 尚桂谷学习笔记07 嵌入式容器 ---Web

    ------配置嵌入式servlet容器------ springboot 默认使用的是嵌入的Servlet(tomcat)容器 问题? 1)如何定制修改Servlet容器的相关配置: 1.修改和se ...

  6. spring boot 尚桂谷学习笔记08 Docker ---Web

    ------Docker------ 简介:Docker是一个开元的应用容器引擎,性能非常高 已经安装好的软件打包成一个镜像放到服务器中运行镜像 MySQL容器,Redis容器...... Docke ...

  7. spring boot 尚桂谷学习笔记06 异常处理 ---Web

    ------错误处理机制------ 默认效果 1 返回一个默认的错误页面 浏览器发送请求的请求头:优先接收 text/html 数据 客户端则默认响应json数据 : accept 没有说明返回什么 ...

  8. springboot 尚桂谷学习笔记03

    ------spring boot 与日志------ 日志框架: 市面上的日志框架: jul jcl jboss-logging logback log4j log4j2 ...... 左边一个门面 ...

  9. Spring实战第六章学习笔记————渲染Web视图

    Spring实战第六章学习笔记----渲染Web视图 理解视图解析 在之前所编写的控制器方法都没有直接产生浏览器所需的HTML.这些方法只是将一些数据传入到模型中然后再将模型传递给一个用来渲染的视图. ...

随机推荐

  1. [BZOJ 3991][SDOI2015]寻宝游戏(dfs序)

    题面 小B最近正在玩一个寻宝游戏,这个游戏的地图中有N个村庄和N-1条道路,并且任何两个村庄之间有且仅有一条路径可达.游戏开始时,玩家可以任意选择一个村庄,瞬间转移到这个村庄,然后可以任意在地图的道路 ...

  2. python-批量解压zip、rar文件

    这是一个用python写解压大量zip脚本的说明,本人新手一个,希望能对各位有所启发. GitHub:https://github.com/lgf133214/Windows-python3- 首先要 ...

  3. lucene版本升级到4.6.0以上之后使用ik分词器遇到的问题

    在将lucene core版本从4.5.1升级到4.7.0后,如下代码使用ik分词器报错 IKAnalyzer analyzer = new IKAnalyzer(true); StringReade ...

  4. LTP安装方法

    git clone https://github.com/linux-test-project/ltp.git apt-get install automake make autotools ./co ...

  5. Laya2.0的转变

    之前一直用Laya1.x+TypeScript了,最近项目开始使用Laya2.0+AS3了 总结一下需要注意的一些事项,算是2种开发模式的区别与过渡吧 1.AS类的访问标识 必须是public,不写会 ...

  6. vue 防抖节流函数——组件封装

    防抖(debounce) 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间. 节流(throttle) 所谓节流,就是指连续触发事件但是在 ...

  7. 搭建阿里云服务器(centos,jdk和Tomcat版本)

    1.购买服务器(登录阿里云,购买服务器,并进入控制台,查看自己的服务器实例 2.域名注册(这步可以省略,直接IP地址访问,因为域名需要备案),购买域名的需要进行解析以及绑定自己的服务器 3.可以准备一 ...

  8. 【转载】windows 开启 nginx 监听80 端口 以及 禁用 http 服务后,无法重启 HTTP 服务,提示 系统错误 123,文件目录、卷标出错

    https://www.cnblogs.com/TianyuSu/p/9509873.html location / { proxy_set_header Host $Host; proxy_set_ ...

  9. shell变量的声明和使用

  10. hdu1210Eddy's 洗牌问题

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1210 Eddy是个ACMer,他不仅喜欢做ACM题,而且对于纸牌也有一定的研究,他在无聊时研究发现,如 ...