64. 购物车-创建数据表

# 注意:没有添加相应的not null约束
CREATE TABLE t_cart (cid INT AUTO_INCREMENT COMMENT '购物车数据id',uid INT COMMENT '用户id',pid INT COMMENT '商品id',num INT COMMENT '商品数量',price BIGINT(20) COMMENT '加入时商品价格(以便做促销,降价了有个差值)',created_user VARCHAR(20) COMMENT '创建人',created_time DATETIME COMMENT '创建时间',modified_user VARCHAR(20) COMMENT '最后修改人',modified_time DATETIME COMMENT '最后修改时间',PRIMARY KEY (cid)
) DEFAULT CHARSET=utf8mb4;

65. 购物车-创建实体类

cn.demo.store.entity包中创建Cart类:

/*** 购物车数据的实体类*/
public class Cart extends BaseEntity {private static final long serialVersionUID = -9051846958681813039L;private Integer cid;private Integer uid;private Integer pid;private Integer num;private Long price;// 添加get,set...等方法}

66. 购物车-加入购物车-持久层

(a) 规划需要的SQL语句

将商品添加到购物车,可能会产生新的购物车数据,也可能是将原有购物车数据中商品数量进行增加!可以判断该用户的购物车中有没有该商品:

select * from t_cart where uid=? and pid=?;

当该用户从来没有把该商品加入到购物车中时,需要插入新的购物车数据,在插入购物车数据时,需要执行的SQL语句大致是:

insert into t_cart (除了cid以外的字段列表) values (值列表);

当该用户的购物车中已经有该商品了,如果反复将该商品加入到购物车,应该只增加商品的数量,即修改原有数据的数量:

update t_cart set num=?, modified_user=?, modified_time=? where cid=?;

(b) 设计抽象方法

cn.demo.store.mapper中创建CartMapper接口,并添加抽象方法:

/*** 处理购物车数据的持久层接口*/
public interface CartMapper {/*** 插入购物车数据* @param cart 购物车数据* @return 受影响的行数*/Integer insert(Cart cart);/*** 修改购物车中商品的数量* @param cid 购物车数据id* @param num 新的数量* @param modifiedUser 最后修改人* @param modifiedTime 最后修改时间* @return 受影响的行数*/Integer updateNumByCid(@Param("cid") Integer cid, @Param("num") Integer num,@Param("modifiedUser") String modifiedUser, @Param("modifiedTime") Date modifiedTime);/*** 查询某用户在购物车添加的某商品的详情* @param uid 用户的id* @param pid 商品的id* @return 匹配的购物车详情,如果该用户没有将该商品添加到购物车,则返回null*/Cart findByUidAndPid(@Param("uid") Integer uid, @Param("pid") Integer pid);}

© 配置映射并测试

src/main/resources/mappers/下创建CartMapper.xml文件,并配置映射:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"      "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"><mapper namespace="cn.tedu.store.mapper.CartMapper"><!-- 插入购物车数据 --><!-- Integer insert(Cart cart) --><insert id="insert" useGeneratedKeys="true" keyProperty="cid">INSERT INTO t_cart (uid, pid, num, price, created_user, created_time, modified_user, modified_time) VALUES (#{uid}, #{pid}, #{num}, #{price}, #{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime})</insert><!-- 修改购物车中商品的数量 --><!-- Integer updateNumByCid(@Param("cid") Integer cid, @Param("num") Integer num,@Param("modifiedUser") String modifiedUser, @Param("modifiedTime") Date modifiedTime); --><update id="updateNumByCid">UPDATEt_cartSETnum=#{num},modified_user=#{modifiedUser},modified_time=#{modifiedTime}WHEREcid=#{cid}</update><!-- 查询某用户在购物车添加的某商品的详情 --><!-- Cart findByUidAndPid(@Param("uid") Integer uid, @Param("pid") Integer pid); --><select id="findByUidAndPid" resultType="cn.tedu.store.entity.Cart">SELECT * FROM t_cart WHERE uid=#{uid} AND pid=#{pid}</select></mapper>

src/test/javacn.demo.store.mapper下创建CartMapperTests测试类,编写并执行单元测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class CartMapperTests {@Autowiredprivate CartMapper mapper;@Testpublic void insert() {Cart cart = new Cart();cart.setUid(1);cart.setPid(2);cart.setNum(3);cart.setPrice(4L);Integer rows = mapper.insert(cart);System.err.println("rows=" + rows);System.err.println(cart);}@Testpublic void updateNumByCid() {Integer cid = 1;Integer num = 10;String modifiedUser = "管理员";Date modifiedTime = new Date();Integer rows = mapper.updateNumByCid(cid, num, modifiedUser, modifiedTime);System.err.println("rows=" + rows);}@Testpublic void findByUidAndPid() {Integer uid = 1;Integer pid = 2;Cart result = mapper.findByUidAndPid(uid, pid);System.err.println(result);}}

67. 购物车-加入购物车-业务层

(a) 规划业务流程、业务逻辑,并创建可能出现的异常

无需创建新的的异常。

(b) 设计抽象方法

创建CartService接口,并在接口中添加抽象方法:

/*** 处理购物车数据的业务层接口*/
public interface CartService {/*** 将商品添加到购物车* @param uid 用户的id* @param username 用户名* @param pid 商品的id* @param amount 添加的商品数量*/void addToCart(Integer uid, String username, Integer pid, Integer amount);}

© 实现抽象方法并测试

创建CartServiceImpl类,实现以上接口,添加@Service注解,添加@Autowired private CartMapper cartMapper;持久层对象,添加@Autowired private ProductService productService;处理商品数据的业务对象:

@Service
public class CartServiceImpl implements CartService {@Autowired private CartMapper cartMapper;@Autowired private ProductService productService;
}

并将持久层的3个抽象方法复制过来,改为私有方法并实现:

  /*** 插入购物车数据* * @param cart 购物车数据*/private void insert(Cart cart) {Integer rows = cartMapper.insert(cart);if (rows != 1) {throw new InsertException("创建购物车数据失败!插入购物车数据时出现未知错误,请联系系统管理员!");}}/*** 修改购物车中商品的数量* * @param cid          购物车数据id* @param num          新的数量* @param modifiedUser 最后修改人* @param modifiedTime 最后修改时间*/private void updateNumByCid(Integer cid, Integer num, String modifiedUser, Date modifiedTime) {Integer rows = cartMapper.updateNumByCid(cid, num, modifiedUser, modifiedTime);if (rows != 1) {throw new UpdateException("修改商品数量失败!更新购物车数据时出现未知错误,请联系系统管理员!");}}/*** 查询某用户在购物车添加的某商品的详情* * @param uid 用户的id* @param pid 商品的id* @return 匹配的购物车详情,如果该用户没有将该商品添加到购物车,则返回null*/private Cart findByUidAndPid(Integer uid, Integer pid) {return cartMapper.findByUidAndPid(uid, pid);}

然后,设计业务接口中定义的抽象方法的实现过程:

  @Overridepublic void addToCart(Integer uid, String username, Integer pid, Integer amount) {// 创建当前时间对象nowDate now = new Date();// 调用findByUidAndPid()查询购物车详情Cart result = findByUidAndPid(uid, pid);// 判断查询结果是否为nullif (result == null) {// 是:表示该用户的购物车没有该商品,则需要执行insert操作// -- 调用productService.getById()得到商品详情,该数据中包含商品价格Product product = productService.getById(pid);// -- 创建新的Cart对象Cart cart = new Cart();// -- 补全Cart对象的属性:uid > 参数uidcart.setUid(uid);// -- 补全Cart对象的属性:pid > 参数pidcart.setPid(pid);// -- 补全Cart对象的属性:num > 参数amountcart.setNum(amount);// -- 补全Cart对象的属性:price > 以上查询到的商品详情中包含价格cart.setPrice(product.getPrice());// -- 补全Cart对象的属性:4个日志 > 参数username, nowcart.setCreatedUser(username);cart.setCreatedTime(now);cart.setModifiedUser(username);cart.setModifiedTime(now);// -- 调用insert()插入数据insert(cart);} else {// 否:表示该用户的购物车已有该商品,则需要执行update操作增加数量// -- 从查询结果中获取cidInteger cid = result.getCid();// -- 从查询结果中取出原有数量,与参数amount相加,得到新的数量Integer num = result.getNum() + amount;// -- 调用updateNumByCid()执行修改数量updateNumByCid(cid, num, username, now);}}

最后,创建CartServiceTests测试类,并测试以上方法:

@RunWith(SpringRunner.class)
@SpringBootTest
public class CartServiceTests {@Autowiredprivate CartService service;@Testpublic void addToCart() {try {Integer uid = 1;String username = "小刘同学";Integer pid = 2;Integer amount = 5;service.addToCart(uid, username, pid, amount);System.err.println("OK.");} catch (ServiceException e) {System.err.println(e.getClass().getName());System.err.println(e.getMessage());}}}

68. 购物车-加入购物车-控制器层

(a) 处理新创建的异常类型

(b) 设计需要处理的请求

  • 请求路径:/carts/add
  • 请求参数:Integer pid, Integer amount, HttpSession session
  • 请求方式:POST
  • 响应数据:JsonResult

© 处理请求并测试

创建CartController控制器类,继承自BaseController,在类的声明之前添加@RestController@RequestMapping("carts")注解,在类中添加@Autowired private CartService cartService;业务层对象:

@RestController
@RequestMapping("carts")
public class CartController extends BaseController {@Autowired private CartService cartService;
}

然后,添加处理请求的方法:

@RestController
@RequestMapping("carts")
public class CartController extends BaseController {@Autowiredprivate CartService cartService;// http://localhost:8080/carts/add?pid=10000017&amount=3@PostMapping("add")public JsonResult<Void> addToCart(Integer pid, Integer amount, HttpSession session) {// 从Session中获取uid和usernameInteger uid = getUidFromSession(session);String username = getUsernameFromSession(session);// 调用业务方法执行功能cartService.addToCart(uid, username, pid, amount);// 响应成功return new JsonResult<>(OK);}}

完成后,打开浏览器,先登录,测试。

69. 购物车-加入购物车-前端页面

// 加入购物车操作
$("#btn-cart-add").click(function(){// 前端对数据进行校验(略)$.ajax({"url":"/carts/add","data":{"pid":id,"amount":$("#num").val()},"type":"post","dateType":"json","success":function(json){if(json.state==200){alert("添加入购物车成功!");}else{alert(json.message);}},"error":function(){alert("您尚未登录或您的登录信息已过期,请重新登录!");}});
});

70. 购物车-显示列表-持久层

(a) 规划需要的SQL语句

显示某用户的购物车数据的SQL语句应该是:

查询的结果中,在显示购物车列表时,还应该显示商品的标题、图片、实时价格等数据,所以,还需要关联商品表一起查询!

当使用关联查询时,由于涉及多张表的数据,查询结果可能有很多列,所以,在使用关联查询,一般不推荐使用星号(*)表示字段列表,而是需要哪些结果,就查哪些字段!同时,为了保证数据的使用,最好把一些关键字段都查出来,即使这些字段还没有想清楚有什么用,例如各种id,都应该查出来!

最后,还要保证查询结果中的列名都是唯一的,才可以正确的封装查询结果,以上查询中,有2个列的名称都是price,所以,至少需要给其中一个price定义别名,使得查询结果中的每个列的名称都不相同:

select cid, uid, pid, t_cart.num, t_cart.price,title, t_product.price AS realPrice, image
from t_cart
left join t_product
on t_cart.pid=t_product.id
where uid=?
order by t_cart.created_time desc

(b) 设计抽象方法

由于涉及关联查询,就必须创建VO类!所以,应该先在cn.demo.store下创建vo包,并根据以上设计并预测的查询结果来创建CartVO类:

/*** 购物车数据的VO类*/
public class CartVO implements Serializable {private static final long serialVersionUID = 461318594743947337L;private Integer cid;private Integer uid;private Integer pid;private Integer num;private Long price;private String title;private Long realPrice;private String image;}

CartMapper接口中添加抽象方法:

  /*** 查询某用户的购物车列表* @param uid 用户id* @return 用户的购物车列表*/List<CartVO> findByUid(Integer uid);

© 配置映射并测试

CartMapper.xml中配置映射:

  <!-- 查询某用户的购物车列表 --><!-- List<CartVO> findByUid(Integer uid) --><select id="findByUid" resultType="cn.tedu.store.vo.CartVO">SELECT cid, uid, pid, t_cart.num, t_cart.price,title, t_product.price AS realPrice, imageFROM t_cart LEFT JOINt_product ON t_cart.pid=t_product.id WHERE uid=#{uid}ORDER BYt_cart.created_time DESC</select>

CartMapperTests中测试:

  @Testpublic void findByUid() {Integer uid = 12;List<CartVO> list = mapper.findByUid(uid);System.err.println("count=" + list.size());for (CartVO item : list) {System.err.println(item);}}

71. 购物车-显示列表-业务层

(a) 规划业务流程、业务逻辑,并创建可能出现的异常

(b) 设计抽象方法

CartService中添加抽象方法:

  /*** 查询某用户的购物车列表* @param uid 用户id* @return 用户的购物车列表*/List<CartVO> getByUid(Integer uid);

© 实现抽象方法并测试

CartServiceImpl中先复制粘贴持久层的方法,私有实现:

  /*** 查询某用户的购物车列表* @param uid 用户id* @return 用户的购物车列表*/private List<CartVO> findByUid(Integer uid) {return cartMapper.findByUid(uid);}

然后,重写接口中的抽象方法:

  @Overridepublic List<CartVO> getByUid(Integer uid) {return findByUid(uid);}

最后,在CartServiceTests中测试:

  @Testpublic void getByUid() {Integer uid = 12;List<CartVO> list = service.getByUid(uid);System.err.println("count=" + list.size());for (CartVO item : list) {System.err.println(item);}}

72. 购物车-显示列表-控制器层

(a) 处理新创建的异常类型

(b) 设计需要处理的请求

  • 请求路径:/carts/
  • 请求参数:HttpSession session
  • 请求方式:GET
  • 响应数据:JsonResult>

© 处理请求并测试

CartController中添加处理请求的方法:

// http://localhost:8080/carts/
@GetMapping({"", "/"})
public JsonResult<List<CartVO>> getByUid(HttpSession session) {// 从Session中获取uid// 执行查询,获取数据// 返回成功与数据
}

完成后,在浏览器中测试。

73. 购物车-显示列表-前端页面

 $(document).ready(function(){showCartList();});function showCartList(){$("#cart-list").empty();$.ajax({"url":"/carts","type":"get","dateType":"json","success":function(json){var arr = json.date;for (var i = 0; i < arr.length; i++) {var html = '<tr>'+ '<td><input type="checkbox" class="ckitem" /></td>'+ '<td><img src="#{image}/collect.png" class="img-responsive" /></td>'+ '<td>#{title}</td>'+ '<td>¥<span id="goodsPrice1">#{realPrice}</span></td>'+ '<td>'+ '<input type="button" value="-" class="num-btn" οnclick="addNum(#{cid},0)" />'+ '<input id="goodsCount1" type="text" size="2" readonly="readonly" class="num-text" value="#{num}">'+ '<input class="num-btn" type="button" value="+" οnclick="addNum(#{cid},1)" />'+ '</td>'+ '<td><span id="goodsCast1">#{totalPrice}</span></td>'+ '<td>'+ '<input type="button" οnclick="delCartItem(this)" class="cart-del btn btn-default btn-xs" value="删除" />'+ '</td>'+ '</tr>';// 使用正则全文匹配html = html.replace(/#{image}/g, arr[i].image); html = html.replace(/#{cid}/g, arr[i].cid); html = html.replace(/#{title}/g, arr[i].title); html = html.replace(/#{realPrice}/g, arr[i].realPrice); html = html.replace(/#{num}/g, arr[i].num); html = html.replace(/#{totalPrice}/g, arr[i].realPrice * arr[i].num);  $("#cart-list").append(html);}}});}

77 . 购物车-增加商品数量-持久层

(a) 规划需要的SQL语句

需要修改购物车中的商品数量,可以使用此前已经开发的updateNumByCid()功能。

在执行修改之前,还应该检查数据的有效性(是否存在,归属是否正确),需要通过查询来实现:

select * from t_cart where cid=?

由于持久层的更新功能只是“根据cid更新num字段的值”,并不体现是“增加数量”还是“减少数量”,所以,还需要取出原本的数量,进行加减,得到新的数量,再执行更新,通过以上查询结果也可以得到原本的数量,便于后续的操作。

(b) 设计抽象方法

CartMapper接口中添加:

  /*** 根据购物车数据的id查询购物车详情* @param cid 购物车数据的id* @return 匹配的购物车详情*/Cart findByCid(Integer cid);

© 配置映射并测试

配置映射:

  <!-- 根据购物车数据的id查询购物车详情 --><!-- Cart findByCid(Integer cid) --><select id="findByCid" resultType="cn.demo.store.entity.Cart">SELECT * FROM t_cart WHERE cid=#{cid}</select>

测试:

  @Testpublic void findByCid() {Integer cid = 10;Cart result = mapper.findByCid(cid);System.err.println(result);}

78. 购物车-增加商品数量-业务层

减少跟增加商品数量的做法是一样的,直接加个参数判断是增加操作还是删除操作(此处略)

(a) 规划业务流程、业务逻辑,并创建可能出现的异常

需要创建CartNotFoundException

(b) 设计抽象方法

CartService中添加:

  /*** 增加购物车中商品的数量* @param cid 购物车数据的id* @param uid 用户的id* @param username 用户名*/void addNum(Integer cid, Integer uid, String username);

© 实现抽象方法并测试

CartServiceImpl中,先将持久层新添加的方法复制过来,改为私有方法实现:

  /*** 根据购物车数据的id查询购物车详情* @param cid 购物车数据的id* @return 匹配的购物车详情*/private Cart findByCid(Integer cid) {return cartMapper.findByCid(cid);}

然后,分析需要重写的接口中的抽象方法:

  @Overridepublic void addNum(Integer cid, Integer uid, String username) {// 根据参数cid调用findByCid()查询购物车详情数据Cart result = findByCid(cid);// 判断查询结果是否为nullif (result == null) {// 是:抛出CartNotFoundExceptionthrow new CartNotFoundException("增加商品数量失败!尝试访问的购物车数据不存在!");}// 判断查询结果中的uid与参数uid是否不匹配if (!result.getUid().equals(uid)) {// 是:抛出AccessDeniedExceptionthrow new AccessDeniedException("增加商品数量失败!非法访问已经被拒绝!");}// 从查询结果中取出原数量,增加1,得到新的数量Integer newNum = result.getNum() + 1;// 判断自定义规则:新的数量应该在哪个范围之内// 调用updateNumByCid()执行更新数量updateNumByCid(cid, newNum, username, new Date());}

测试:

  @Testpublic void addNum() {try {Integer cid = 10;Integer uid = 12;String username = "小刘同学";service.addNum(cid, uid, username);System.err.println("OK.");} catch (ServiceException e) {System.err.println(e.getClass().getName());System.err.println(e.getMessage());}}

79. 购物车-增加商品数量-控制器层

(a) 处理新创建的异常类型

需要处理CartNotFoundException

(b) 设计需要处理的请求

  • 请求路径:/carts/{cid}/num/add
  • 请求参数:@PathVariable("cid") Integer cid, HttpSession session
  • 请求方式:POST
  • 响应数据:JsonResult

© 处理请求并测试

CartController 中处理请求:

  // http://localhost:8080/carts/11/num/add@PostMapping("{cid}/num/add")public JsonResult<Void> addNum(@PathVariable("cid") Integer cid, HttpSession session) {Integer uid = getUidFromSession(session);String username = getUsernameFromSession(session);cartService.addNum(cid, uid, username);return new JsonResult<>(OK);}

完成后,在浏览器中测试。

80. 购物车-增加商品数量-前端页面

+ '<input type="button" value="-" class="num-btn" οnclick="addNum(#{cid},0)" />'+ '<input id="goodsCount1" type="text" size="2" readonly="readonly" class="num-text" value="#{num}">'+ '<input class="num-btn" type="button" value="+" οnclick="addNum(#{cid},1)" />'/*** 将此购物商品数量加1/减1* @Param:cid 购物车的cid*/
function addNum(cid, type) {$.ajax({"url":"/carts/"+cid+"/num/add/"+type,"type":"post","dateType":"json","success":function(json){if(json.state!=200){alert(json.message);}showCartList();},"error":function(){alert("您的登录信息已过期,请重新登录!");}});
}

SpringBoot-项目4-购物车(添加入购物车,购物车列表,购物车商品数量加减操作)相关推荐

  1. php购物车数量加减代码,js实现购物车商品数量加减

    本文实例为大家分享了js实现购物车商品数量加减的具体代码,供大家参考,具体内容如下 Html - + CSS .list a { display: block; margin: 30px; paddi ...

  2. jQuery购物车 商品数量加减和小计

    整体代码: <!DOCTYPE html> <html><head><meta charset="utf-8"><title& ...

  3. php购物车数量加减代码,购物车商品数量加减效果

    Insert title here $(function(){ $(".add").on("click", function(){ // 先找到当前加号的父元素 ...

  4. 微信小程序购物车 数量加减功能

    微信小程序购物车 数量加减功能 wxml <!-- 主容器 --> <view class="stepper"> <!-- 减号 --> < ...

  5. 购物车之商品数量加加

    通常添加购物车的时候会出现添加添加重复的情况,如果重复则让存在的商品个数加一,不重复则添加 具体效果 添加购物车 具体思路 通过点击事件触发vuex的commit方法将商品数据传入vuex中 在vue ...

  6. android 购物车数量加减计算(几行代码实现效果)

    就是使用++ 和-- 逻辑简单,直接上代码吧 已经使用到项目 ,具体添加的时候请求后台的接口,在添加即可..项目使用没有任何差错,简单明了... private TextView textView;p ...

  7. springboot项目报错, 错误:找不到或无法加载主类com.xxx.xxx.Application

    springboot项目报,错误:找不到或无法加载主类com.xxx.xxx.Application 1.打开配置设置Edit Configurations- 2.点击+号,找到spring boot ...

  8. vue小实验——小米商城购物车,合计、结算、数量加减

    一.简述 采用vue实现购物车的功能,主要是对购物车内的数量.价格.合计等,话不多说直接放图. 二.上代码 1.html板块 这个部分每个人的布局排版都不一样你们可以适当参考,可能我的布局显得很臃肿 ...

  9. 小程序购物车全选反选 数量加减 总价钱 总数量

    //不多说了,只是为了记录每次做的小东西 ,有需要的可以直接复制代码到开发工具查看比较清楚,上代码 <!--pages/map/map.wxml--> <view class='co ...

最新文章

  1. 某程序员炫耀:因长得太帅被女领导追求,三年就提拔到总监!程序员搞技术没用,健身护肤才是王道!...
  2. 优先级调度和运行前调度的比较
  3. 电子商务网站的经验教训
  4. seo网站营销与内容营销不可分割
  5. ajax图片上传(asp.net +jquery+ashx)
  6. 【活动(深圳)DevOps/.NET 微服务 秋季分享会】火热报名中!
  7. HTMLTESTRunner自动化测试报告增加截图功能
  8. AES、DES加解密方法(Java和JS编程)
  9. 怎么查看ingress的规则_Prometheus PormQL语法及告警规则写法
  10. html登录页面代码实现原理,web登录代码
  11. readelf使用说明
  12. Sybase数据库知识总结
  13. 如何使用SPSS进行斯皮尔曼相关性分析
  14. 织梦栏目地址使用栏目名称首字母
  15. Android Recovery OTA升级(二)—— Recovery源码解析
  16. chrome浏览器怎么模拟手机访问网页(已測OK)
  17. 贵州大学oj C++ 第五次 12.房产税费计算
  18. c语言处理nc程序,NC程序的语言问题
  19. 吴华伟(帮别人名字作诗)
  20. 在计算机中有什么作用,内存是什么在电脑中有什么作用

热门文章

  1. c++实现运动目标的追踪
  2. C#委托和事件的概念
  3. python--unicodedata用法
  4. 女人也真不容易(续)——37度男人
  5. 01-linux磁盘管理-02-RAID(RAID1,RAID10,RAID01,RAID5,创建RAID,查看RAID,删除RAID)
  6. Python使用ffmpy将amr格式的音频转化为mp3格式
  7. QT获取调色板rgb色值
  8. css蒙层和新手引导的实现方式
  9. 【兄弟反目成仇系列】:我只是写了一个爆炸信息窗口脚本,好兄弟追了我几条街~
  10. 人机大战硝烟再起:阿尔法狗升级了 柯洁拼了