Java-springboot生鲜电商项目(四)商品模块
Java-springboot生鲜电商项目(四)商品模块
- 商品模块
- 涉及到的接口
- 这个模块的难点
- 常见错误
- (一)后台的新增商品
- 在request创建AddProduct类,目的是为了修改product不会污染pojo下的product类
- 在productMapper创建查询商品名的接口,用于之后的重名判断
- 在productMapper.xml编写SQL语句
- 在productServiceImpl实现商品插入的业务逻辑
- 写到这里,会出现一个问题,就是图片上传问题,处理步骤如下:
- UUID
- 在Constant常量类中编写图片文件保存的地址并在类上加入`@Component`注解
- 在配置文件是配置本地电脑保存图片文件的路径,我在这里踩了一个坑,我在文件夹后面少了一个"/"导致postman测试不到,一直报20000系统错误
- 在MallExceptionEnum中加入
- 在productController中编写增加商品和图片上传的接口
- 在postman中进行商品添加和添加图片的操作
- 测试都没毛病,但是,我把图片地址信息放在浏览器上,会出现报错信息,打不开图片:原因是关于自定义静态目录资源映射目录这个点
- 遇到这个问题需要在config下的MallWebMvcConfig进行配置地址映射,添加上
- (二)后台的更新商品接口
- 在request新增UpdateProduct
- 在ProductServiceImpl中创建更新商品的业务逻辑
- 在Productcontroller实现更新操作
- (三)后台的删除商品接口实现
- 在productServiceImpl根据传入的ID进行商品的删除
- 在ProductController中实现删除商品
- 使用postman进行接口的测试
- (四)后台的批量上下架(难点)
- dao层接口编写
- mapper.xml SQL编写
- serviceImple层接口编写
- controller层接口编写
- postman测试接口
- (五)前台商品列表接口开发
- dao
- mapper
- serviceImpl
- controller
- (六)前台商品详情接口开发
- serviceImpl,直接复用之前的业务逻辑,不用重新从底层编写
- controller
- (七)前台商品列表接口(非常难)
- 具体功能实现
- 前台商品列表的类
- 因为目录处理:如果查询某个目录下的商品,不仅需要查出来该目录,还需要查询处理子目录的所有商品。所有再新建一个quey包,新建ProductListQuery类,包含商品列表集合
- 因为用户查询的目录不是所有的目录,所以在之前编写的categoryService.listCategoryForCustomer需要进行重构:将parentId变成是用户自己传进来
- 改动完后,还需要对service层和controller层进行同步的改动
- 排序处理
- 在constant包中创建支持排序的规则
- dao
- mapper
- service
- controller
- postman
商品模块
涉及到的接口
- 增加商品
- 上传图片
- 更新商品
- 删除商品
- 批量上下架商品
- 商品列表(后台)
- 前台:商品列表
- 商品详情
这个模块的难点
- 商品搜索
- 排序
- 目录查询
常见错误
- 更新和新增放在同一个接口
- 排序字段不用枚举
(一)后台的新增商品
在request创建AddProduct类,目的是为了修改product不会污染pojo下的product类
package com.hyb.mall.model.request;import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.Date;public class AddProductReq {@NotNull(message = "商品名称不能为空")private String name;@NotNull(message = "商品图片不能为空")private String image;private String detail;@NotNull(message = "商品分类不能为空")private Integer categoryId;@NotNull(message = "商品价格不能为空")@Min(value = 1, message = "价格不能小于1分钱")private Integer price;@NotNull(message = "商品库存不能为空")@Max(value = 10000, message = "库存不能大于1万")private Integer stock;private Integer status;public String getName() {return name;}public void setName(String name) {this.name = name == null ? null : name.trim();}public String getImage() {return image;}public void setImage(String image) {this.image = image == null ? null : image.trim();}public String getDetail() {return detail;}public void setDetail(String detail) {this.detail = detail == null ? null : detail.trim();}public Integer getCategoryId() {return categoryId;}public void setCategoryId(Integer categoryId) {this.categoryId = categoryId;}public Integer getPrice() {return price;}public void setPrice(Integer price) {this.price = price;}public Integer getStock() {return stock;}public void setStock(Integer stock) {this.stock = stock;}public Integer getStatus() {return status;}public void setStatus(Integer status) {this.status = status;}
}
在productMapper创建查询商品名的接口,用于之后的重名判断
Product selectByName(String name);
在productMapper.xml编写SQL语句
<select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">select<include refid="Base_Column_List"/>from imooc_mall_productwhere name=#{name,jdbcType=VARCHAR}</select>
在productServiceImpl实现商品插入的业务逻辑
package com.hyb.mall.service.impl;import com.hyb.mall.exception.MallException;
import com.hyb.mall.exception.MallExceptionEnum;
import com.hyb.mall.model.dao.ProductMapper;
import com.hyb.mall.model.pojo.Product;
import com.hyb.mall.model.request.AddProductReq;
import com.hyb.mall.service.ProductService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class ProductServiceImpl implements ProductService {@AutowiredProductMapper productMapper;@Overridepublic void add(AddProductReq addProductReq) {//1.创建product实例Product product = new Product();//2.将新增的product复制到pojo中product类中,对数据进行一次覆盖BeanUtils.copyProperties(addProductReq, product);//3.通过查询数据库是否有重名的商品,有就添加失败Product productOld = productMapper.selectByName(addProductReq.getName());if (productOld != null) {throw new MallException(MallExceptionEnum.NAME_EXISTED);}//4.进行插入数据操作并判断是否有效插入int count = productMapper.insertSelective(product);if (count == 0) {throw new MallException(MallExceptionEnum.CREATE_FAILE);}}
}
写到这里,会出现一个问题,就是图片上传问题,处理步骤如下:
UUID
- 使用UUID(通用唯一识别码)在本地上传,风险不大,在服务器上传图片,如果有重名就会将原来的图片覆盖掉,也是防止爬图
- UUID的生成规则:日期和时间,Mac地址,hashcode,随机数
在Constant常量类中编写图片文件保存的地址并在类上加入@Component
注解
//因为存在静态的static,用普通的方式进行处理,是注入不进去的 在进行图片上传的时候回报错public static String FILE_UPLOAD_DIR;@Value("${file.upload.dir}")public void setFileUploadDir(String fileUploadDir){FILE_UPLOAD_DIR=fileUploadDir;}
在配置文件是配置本地电脑保存图片文件的路径,我在这里踩了一个坑,我在文件夹后面少了一个"/"导致postman测试不到,一直报20000系统错误
file.upload.dir=/Users/hyb/Desktop/Pfile/
在MallExceptionEnum中加入
MKDIR_FAILE(10014,"文件夹创建失败"),
在productController中编写增加商品和图片上传的接口
package com.hyb.mall.controller;import com.hyb.mall.common.ApiRestResponse;
import com.hyb.mall.common.Constant;
import com.hyb.mall.exception.MallException;
import com.hyb.mall.exception.MallExceptionEnum;
import com.hyb.mall.model.request.AddProductReq;
import com.hyb.mall.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;/*** 描述:后台管理Controller*/
@RestController
public class ProductAdminController {@AutowiredProductService productService;@PostMapping("admin/product/add")public ApiRestResponse addProduct(@Valid @RequestBody AddProductReq addProductReq) {productService.add(addProductReq);return ApiRestResponse.success();}@PostMapping("admin/upload/file")public ApiRestResponse upload(HttpServletRequest httpServletRequest,@RequestParam("file") MultipartFile file) {//获取原始名字String fileName = file.getOriginalFilename();//获取名字后缀String suffixName = fileName.substring(fileName.lastIndexOf("."));//生成UUIDUUID uuid = UUID.randomUUID();String newFileName = uuid.toString() + suffixName;//创建文件File fileDirectory = new File(Constant.FILE_UPLOAD_DIR);File destFile = new File(Constant.FILE_UPLOAD_DIR + newFileName);//如果文件夹不存在则新建文件夹if (!fileDirectory.exists()) {if (!fileDirectory.mkdir()) {throw new MallException(MallExceptionEnum.MKDIR_FAILE);}}try {file.transferTo(destFile);} catch (IOException e) {e.printStackTrace();}try {return ApiRestResponse.success(getHost(new URI(httpServletRequest.getRequestURI()+""))+"/image/"+newFileName);} catch (URISyntaxException e) {return ApiRestResponse.error(MallExceptionEnum.UPLOAD_FAILE);}}/*** 获取IP和端口号*/private URI getHost(URI uri) {URI effectiveURI;try {effectiveURI = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),null, null, null);} catch (URISyntaxException e) {effectiveURI = null;}return effectiveURI;}
}
在postman中进行商品添加和添加图片的操作
测试都没毛病,但是,我把图片地址信息放在浏览器上,会出现报错信息,打不开图片:原因是关于自定义静态目录资源映射目录这个点
遇到这个问题需要在config下的MallWebMvcConfig进行配置地址映射,添加上
//图片回传registry.addResourceHandler("/images/**").addResourceLocations("file:"+ Constant.FILE_UPLOAD_DIR);
(二)后台的更新商品接口
在request新增UpdateProduct
package com.hyb.mall.model.request;import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.Date;
/*** 描述:更新商品*/
public class UpdateProductReq {@NotNull(message = "商品id不能为空")private Integer id;private String name;private String image;private String detail;private Integer categoryId;@Min(value = 1, message = "价格不能小于1分钱")private Integer price;@Max(value = 10000, message = "库存不能大于1万")private Integer stock;private Integer status;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name == null ? null : name.trim();}public String getImage() {return image;}public void setImage(String image) {this.image = image == null ? null : image.trim();}public String getDetail() {return detail;}public void setDetail(String detail) {this.detail = detail == null ? null : detail.trim();}public Integer getCategoryId() {return categoryId;}public void setCategoryId(Integer categoryId) {this.categoryId = categoryId;}public Integer getPrice() {return price;}public void setPrice(Integer price) {this.price = price;}public Integer getStock() {return stock;}public void setStock(Integer stock) {this.stock = stock;}public Integer getStatus() {return status;}public void setStatus(Integer status) {this.status = status;}
}
在ProductServiceImpl中创建更新商品的业务逻辑
@Overridepublic void update(Product updateProduct){//1.查询是否有相同的商品名Product productOld = productMapper.selectByName(updateProduct.getName());//2.同名且不同id不能继续修改if (productOld != null && productOld.getId().equals(updateProduct.getId())) {throw new MallException(MallExceptionEnum.NAME_EXISTED);}//3.通过校验int count = productMapper.updateByPrimaryKeySelective(updateProduct);if (count == 0) {throw new MallException(MallExceptionEnum.UPDATE_FAILED);}}
在Productcontroller实现更新操作
@ApiOperation("后台更新商品")@PostMapping("admin/product/update")public ApiRestResponse update(@Valid @RequestBody UpdateCategoryReq updateCategoryReq){Product product = new Product();BeanUtils.copyProperties(updateCategoryReq,product);productService.update(product);return ApiRestResponse.success();}
(三)后台的删除商品接口实现
在productServiceImpl根据传入的ID进行商品的删除
@Overridepublic void delete(Integer id) {//1.查询商品的id主键是否存在Product productOld = productMapper.selectByPrimaryKey(id);if (productOld == null) {throw new MallException(MallExceptionEnum.DELETE_FAILE);}//2.存在商品的id则进行删除操作int count = productMapper.deleteByPrimaryKey(id);if (count == 0) {throw new MallException(MallExceptionEnum.DELETE_FAILE);}}
在ProductController中实现删除商品
@ApiOperation("后台删除商品")@PostMapping("admin/product/delete")public ApiRestResponse delete(@RequestParam Integer id){productService.delete(id);return ApiRestResponse.success();}
使用postman进行接口的测试
(四)后台的批量上下架(难点)
dao层接口编写
int batchUpdateSellStatus(@Param("ids") Integer[] ids, @Param("sellStatus") Integer sellStatus);
mapper.xml SQL编写
<update id="batchUpdateSellStatus" >update imooc_mall_productset status = #{sellStatus}where id in<foreach collection="ids" close=")" item="id" open="(" separator=",">#{id}</foreach></update>
serviceImple层接口编写
@Overridepublic void batchUpdateSellStatus(Integer[] ids, Integer sellStatus){productMapper.batchUpdateSellStatus(ids,sellStatus);}
controller层接口编写
@ApiOperation("后台批量上下架")@PostMapping("admin/product/batchUpdateSellStatus")public ApiRestResponse batchUpdateSellStatus(@RequestParam Integer[] ids,@RequestParam Integer sellStatus){productService.batchUpdateSellStatus(ids,sellStatus);return ApiRestResponse.success();}
postman测试接口
(五)前台商品列表接口开发
dao
List<Product> selectListForAdmin();
mapper
<select id="selectListForAdmin" resultMap="BaseResultMap">select<include refid="Base_Column_List"/>from imooc_mall_productorder by update_time desc</select>
serviceImpl
@Overridepublic PageInfo listForAdmin(Integer pageNum, Integer pageSize){PageHelper.startPage(pageNum,pageSize);List<Product> products = productMapper.selectListForAdmin();PageInfo pageInfo = new PageInfo(products);return pageInfo;}
controller
@ApiOperation("商品列表")@PostMapping("admin/product/list")public ApiRestResponse list(@RequestParam Integer pageNum,@RequestParam Integer pageSize) {PageInfo pageInfo = productService.listForAdmin(pageNum, pageSize);return ApiRestResponse.success(pageInfo);}
(六)前台商品详情接口开发
serviceImpl,直接复用之前的业务逻辑,不用重新从底层编写
@Overridepublic Product detail(Integer id){Product product = productMapper.selectByPrimaryKey(id);return product;}
controller
@ApiOperation("商品详情")@PostMapping("product/detail")public ApiRestResponse detail(@RequestParam Integer id){Product detail = productService.detail(id);return ApiRestResponse.success(detail);}
(七)前台商品列表接口(非常难)
具体功能实现
- 入参判空
- 加%通配符
- like关键字
前台商品列表的类
package com.hyb.mall.model.request;
public class ProductListReq {private String keyword;private Integer categoryId;private String orderBy;private Integer pageNum = 1;private Integer pageSize = 10;public String getKeyword() {return keyword;}public void setKeyword(String keyword) {this.keyword = keyword;}public Integer getCategoryId() {return categoryId;}public void setCategoryId(Integer categoryId) {this.categoryId = categoryId;}public String getOrderBy() {return orderBy;}public void setOrderBy(String orderBy) {this.orderBy = orderBy;}public Integer getPageNum() {return pageNum;}public void setPageNum(Integer pageNum) {this.pageNum = pageNum;}public Integer getPageSize() {return pageSize;}public void setPageSize(Integer pageSize) {this.pageSize = pageSize;}
}
因为目录处理:如果查询某个目录下的商品,不仅需要查出来该目录,还需要查询处理子目录的所有商品。所有再新建一个quey包,新建ProductListQuery类,包含商品列表集合
package com.hyb.mall.query;import java.util.List;/*** 描述:查询前台商品列表的query*/
public class ProductListQuery {//1.关键字private String keyword;//2.商品列表private List<Integer> categoryIds;public String getKeyWord() {return keyword;}public void setKeyWord(String keyWord) {this.keyWord = keyword;}public List<Integer> getCategoryIds() {return categoryIds;}public void setCategoryIds(List<Integer> categoryIds) {this.categoryIds = categoryIds;}
}
因为用户查询的目录不是所有的目录,所以在之前编写的categoryService.listCategoryForCustomer需要进行重构:将parentId变成是用户自己传进来
@Override@Cacheable(value = "listCategoryForCustomer") //是spring所提供的public List<CategoryVO> listCategoryForCustomer(Integer parentId){ArrayList<CategoryVO> categoryVOList = new ArrayList<>();recursivelyFindCategories(categoryVOList,parentId);return categoryVOList;}
改动完后,还需要对service层和controller层进行同步的改动
List<CategoryVO> listCategoryForCustomer(Integer parentId);
@ApiOperation("前台商品分类列表")@PostMapping("category/list")@ResponseBodypublic ApiRestResponse listCategoryForCustomer(){//parentId设置成0,就能查询所有的数据List<CategoryVO> categoryVOS = categoryService.listCategoryForCustomer(0);return ApiRestResponse.success(categoryVOS);}
排序处理
在constant包中创建支持排序的规则
public interface ProductListOrderBy{Set<String> PRICE_ASC_DESC = Sets.newHashSet("price desc","price asc");}
dao
List<Product> selectList(@Param("query") ProductListQuery query);
mapper
<select id="selectList" parameterType="com.hyb.mall.query.ProductListQuery" resultMap="BaseResultMap">select<include refid="Base_Column_List"/>from imooc_mall_product<where><if test="query.keyword != null">and name like #{query.keyword}</if><if test="query.categoryIds != null">and category_id in<foreach collection="query.categoryIds" close=")" item="item" open="(" separator=",">#{item}</foreach></if>and status = 1</where>order by update_time desc</select>
service
@Overridepublic PageInfo list(ProductListReq productListReq) {//构建query对象ProductListQuery productListQuery = new ProductListQuery();//搜索处理if (!StringUtils.isEmpty(productListReq.getKeyword())) {//合成字符串,就能利用数据库的模糊查找功能String keyword = new StringBuilder().append("%").append(productListReq.getKeyword()).append("%").toString();productListQuery.setKeyWord(keyword);}//3.目录处理,如果查某个目录下的商品,不仅需要查出该目录下的,还需要把所有子目录的所有商品查出来,所以要拿到一个目录id的Listif (productListReq.getCategoryId() != null) {//这里需要对listCategoryForCustomer进行重构,List<CategoryVO> categoryVOSList = categoryService.listCategoryForCustomer(productListReq.getCategoryId());//得到的categoryVOSList是一个树状结构,需要进行平铺展开,将子节点的ID都拿过来ArrayList<Integer> categoryIds = new ArrayList<>();categoryIds.add(productListReq.getCategoryId());getCategoryIds(categoryVOSList, categoryIds);productListQuery.setCategoryIds(categoryIds);}//排序处理//从前端请求拿到orderbyString orderBy = productListReq.getOrderBy();if (Constant.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)) {PageHelper.startPage(productListReq.getPageNum(), productListReq.getPageSize(), orderBy);} else {PageHelper.startPage(productListReq.getPageNum(), productListReq.getPageSize());}List<Product> productList = productMapper.selectList(productListQuery);PageInfo pageInfo = new PageInfo(productList);return pageInfo;}private void getCategoryIds(List<CategoryVO> categoryVOList, ArrayList<Integer> categoryIds) {for (int i = 0; i < categoryVOList.size(); i++) {CategoryVO categoryVO = categoryVOList.get(i);if (categoryVO != null) {categoryIds.add(categoryVO.getId());getCategoryIds(categoryVO.getChildCategory(), categoryIds);}}}
controller
@ApiOperation("前台商品列表")@PostMapping("product/list")public ApiRestResponse list(@RequestParam ProductListReq productListReq){PageInfo list = productService.list(productListReq);return ApiRestResponse.success(list);}
postman
Java-springboot生鲜电商项目(四)商品模块相关推荐
- java spu sku_SpringBoot电商项目实战 — 商品的SPU/SKU实现
最近事情有点多,所以系列文章已停止好多天了.今天我们继续Springboot电商项目实战系列文章.到目前为止,整个项目的架构和基础服务已经全部实现,分布式锁也已经讲过了.那么,现在应该到数据库设计及代 ...
- Java开源生鲜电商平台-Java分布式以及负载均衡架构与设计详解(源码可下载)
Java开源生鲜电商平台-Java分布式以及负载均衡架构与设计详解(源码可下载) 说明:主要是针对一些中大型的项目需要进行分布式以及负载均衡的架构提一些思路与建议. 面对大量用户访问.高并发请求,海量 ...
- Java生鲜电商平台-团购模块设计与架构
Java生鲜电商平台-团购模块设计与架构 说明:任何一个电商系统中,对于促销这块是必不可少的,毕竟这块是最吸引用户的,用户也是最爱的模块之一,理由很简单,便宜. 我的经验是无论是大的餐饮点还是小的餐饮 ...
- Java开源生鲜电商平台-深刻理解电商的库存架构与解决方案(源码可下载)
https://www.cnblogs.com/jurendage/p/9227283.html 说明:一般电商的库存都是跟SKU相关联的,那么怎么样才能进行SKU的库存管理呢?有以下几种方式与方法: ...
- java 团购开发_Java生鲜电商平台-团购模块设计与架构
Java生鲜电商平台-团购模块设计与架构 说明:任何一个电商系统中,对于促销这块是必不可少的,毕竟这块是最吸引用户的,用户也是最爱的模块之一,理由很简单,便宜. 我的经验是无论是大的餐饮点还是小的餐饮 ...
- [附源码]计算机毕业设计JAVA社区生鲜电商平台
[附源码]计算机毕业设计JAVA社区生鲜电商平台 项目运行 环境配置: Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(Inte ...
- Java生鲜电商平台-订单配送模块的架构与设计
Java生鲜电商平台-订单配送模块的架构与设计 生鲜电商系统最终的目的还是用户下单支付购买, 所以订单管理系统是电商系统中最为复杂的系统,其作为中枢决定着整个商城的运转, 本文将对于生鲜类电商平台的订 ...
- JS任务机制 - springboot实战电商项目mall4j
springboot实战电商项目mall4j (https://gitee.com/gz-yami/mall4j) java商城系统源码 1.介绍 工作一段时间了,今天在这总结一下浏览器执行JS任务机 ...
- Java开源生鲜电商平台-订单抽成模块的设计与架构(源码可下载)
Java开源生鲜电商平台-订单抽成模块的设计与架构(源码可下载) 说明:订单抽成指的是向卖家收取相应的信息服务费.(目前市场上有两种抽成方式,一种是按照总额的抽成比率,另外一种是按照订单明细的抽成比率 ...
- Java开源生鲜电商平台-支付模块的设计与架构(源码可下载
Java开源生鲜电商平台-支付模块的设计与架构(源码可下载) Java开源生鲜电商平台-支付模块的设计与架构(源码可下载) 开源生鲜电商平台支付目前支持支付宝与微信.针对的是APP端(android ...
最新文章
- 网络地址转换(PAT)
- 工作后越来越觉得,丢什么也不能丢账号!
- C#的winform矩阵简单运算
- python—操作MySQL增加数据
- IOC操作Bean管理XML方式(注入内部 bean 和 级联赋值)
- centos利用nexus搭建局域网docker私有仓库
- Vuejs 动态绑定属性
- php zrem,Redis Zrem 命令
- sonarqube查看问题
- java autointeger_【Java多线程】线程安全的Integer,AutomicInteger
- 深度学习优化算法大全系列1:概览
- 讯飞输入法第11次作答:效率升维、场景细分、个性满足
- 京东支付逻辑存在不安全因素
- 奥比中光深度摄像头_IphoneX发布!奥比中光也可提供3D深度摄像头
- 公众号零基础,只需10分钟,你的公众号也能5天500+粉丝
- java 蜂鸣器_蜂鸣器的介绍
- 微软bing每日壁纸API接口
- 基于halcon的二维椭圆测量实例
- 时序预测之三_傅立叶和小波变换
- 【Meetup讲师】您有一张社区认证讲师证书未领取,点击领取!
热门文章
- 韦小宝丝绸|如何鉴别香云纱可以用以下六种方法
- 电脑计算机主板不启动,电脑主板不能启动的解决方法
- php随浏览器大小变化,如何在将图像显示到浏览器之前使用php重新调整图像大小?...
- Java多线程篇--基本概念
- ubuntu16 下安装 dnw 给开发板传输文件,出现的问题以及解决方法
- 3ds max基础材质初学者必读(27)——使用Arch Design材质
- Office(Word/Excel/PPT)问题集
- autocad application 版本
- PyAutoGUI库-模拟鼠标键盘操作
- 鸟哥惠新宸:PHP 7.1 的新特性我并不是很喜欢