• 企业微信点餐系统读后感


    1:架构示例图

    2:mysql中utf8和utf8mb4区别

        那上面说了既然utf8能够存下大部分中文汉字,那为什么还要使用utf8mb4呢? 原来mysql支持的 utf8 编码最大字符长度为 3 字节,如果遇到 4 字节的宽字符就会插入异常了。三个字节的 UTF-8 最大能编码的 Unicode 字符是 0xffff,也就是 Unicode 中的基本多文种平面(BMP)。也就是说,任何不在基本多文本平面的 Unicode字符,都无法使用 Mysql 的 utf8 字符集存储。包括 Emoji 表情(Emoji 是一种特殊的 Unicode 编码,常见于 ios 和 android 手机上),和很多不常用的汉字,以及任何新增的 Unicode 字符等等。

    3:数据库连接包

    <!-- 操纵数据库的工具 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!--Mysql数据库连接包-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>

    4:jpa访问数据库的日志打印配置
    # 打印SQL
    show-sql: true

     5:数据库对象实体配置信息

    //@Table(name = "product_category")//如果类名和表名不是一致的话就需要
    @Entity
    @DynamicUpdate //动态更新-需要设置数据库的更新时间字段为自动更新 这样,查询出时间,去设置其他字段后保存,更新时间依然会更新
    @Data //不用写setter和getter方法,toString也可以省了 性能是一样的,可以去看编译的class文件,和我们写的一样
    //@Getter //不用写getter方法
    //@Setter //不用写setter方法
    public class ProductCategory {
    /**
    * 类目ID
    */
    @Id//主键id
    @GeneratedValue //自增
    private Integer categoryId;

    //    /**
    // * 关联订单详情
    // */
    // @Transient //在数据库对应的时候能忽略,就不会去数据库找对应的字段了
    // private List<OrderDetail> orderDetailList;
    /**
    * 订单状态,默认为新下单
    */
    private Integer orderStatus = OrderStausEnum.NEW.getCode();
    //如果有构造方法必须添加一个默认的
    public ProductCategory() {
    }

    public ProductCategory(String categoryName, Integer categoryType) {
    this.categoryName = categoryName;
    this.categoryType = categoryType;
    }

    
    

    }
    6:jpa访问数据库
    public interface ProductCategoryDao extends JpaRepository<ProductCategory,Integer>{//后面的是主键类型

    /**
    * 通过categoryType集合 查询出ProductCategory集合
    * @param categoryTypeList
    * @return
    */
    List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList);
    }
    public interface OrderDetailDao extends JpaRepository<OrderDetail,String> {

    /**
    * 查询订单详情 - 根据订单id
    * @param orderId
    * @return
    */
    List<OrderDetail> findByOrderId(String orderId);

    }

    7:lombok 使用
    <!--
    实体类使用@Data注解 不用再写setter、getter方法
    类引入日志 使用@Slf4j注解
    IDEA记得装lombok插件
    -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>

    8: 调试断言应用

    @Test
    @Transactional //在测试里面的事务是完全回滚,运行完就回滚
    public void saveTest(){
    ProductCategory productCategory = new ProductCategory("女生最爱",5);
    ProductCategory result = productCategoryDao.save(productCategory);
    Assert.assertNotNull(result);
    // Assert.assertNotEquals(null,result);//不期望是null,期望是result 和上面是一样的
    //写得统一一点的话,可以这样
    //Assert.assertTrue("查询所有的订单列表应该大于0",orderDTOPage.getTotalElements()>0);//查询总数大于0

    }

    @Test
    public void findOne() throws Exception {
    ProductCategory productCategory = productCategoryService.findOne(1);
    Assert.assertEquals(new Integer(1),productCategory.getCategoryId());
    }

    @Test
    public void findAll() throws Exception {
    List<ProductCategory> productCategoryList = productCategoryService.findAll();
    Assert.assertNotEquals(0,productCategoryList.size());
    }


    9:Arrays.asList 方法
    List<Integer> list = Arrays.asList(2,3,4);
    List<ProductInfoVO> productInfoVOList = new ArrayList<>();

    Map<String,String> map = new HashMap<>();
    map.put("orderId",createResult.getOrderId());

    10:测试选择注入的类

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ProductCategoryServiceImplTest {

    @Autowired
    private ProductCategoryServiceImpl productCategoryService;//这里选择实现,因为是实现的测试
    }
    11:分页

    @GetMapping("/list")
    public ModelAndView list(@RequestParam(value = "page",defaultValue = "1")Integer page,
    @RequestParam(value = "size",defaultValue = "10")Integer size,
    Map<String,Object> map){//map - 模板数据返回到页面
    PageRequest pageRequest = new PageRequest(page-1,size);
    Page<OrderDTO> orderDTOPage = orderService.findList(pageRequest);
    map.put("orderDTOPage",orderDTOPage);
    map.put("currentPage",page);//当前页
    map.put("size",size);//一页有多少数据
    return new ModelAndView("order/list",map);
    }



    Page<OrderMaster> findByBuyerOpenid(String buyerOpenid, Pageable pageable);
    @Test
    public void findList() throws Exception {
    PageRequest pageRequest = new PageRequest(0,2);
    Page<OrderDTO> orderDTOPage = orderService.findList(BUYER_OPENID,pageRequest);
    log.info("list = {}",orderDTOPage.getContent());
    Assert.assertNotEquals(0,orderDTOPage.getTotalElements());
    }

    @Override
    public Page<OrderDTO> findList(Pageable pageable) {
    Page<OrderMaster> orderMasterPage =orderMasterDao.findAll(pageable);//分页查询
    List<OrderDTO> orderDTOList = OrderMaster2OrderDTOConverter.convert(orderMasterPage.getContent());
    return new PageImpl<>(orderDTOList,pageable,orderMasterPage.getTotalElements());
    }


    12:枚举的使用
    @Getter
    public enum OrderStausEnum implements CodeEnum<Integer> {
    NEW(0,"新订单"),
    FINISHED(1,"完结"),
    CANCEL(2,"已取消"),
    ;
    private Integer code;

    private String message;

    OrderStausEnum(Integer code, String message) {
    this.code = code;
    this.message = message;
    }

    /**
    * 通过cide获取订单状态枚举
    * 不能这么写,如果还有另外的枚举状态,就还得复制拷贝过去
    * 所以用实现接口和工具类来实现
    * @param code
    * @return
    */
    // public static OrderStausEnum getOrderStatusEnum(Integer code){
    // for(OrderStausEnum orderStausEnum:OrderStausEnum.values()){
    // if(orderStausEnum.getCode().equals(code)){
    // return orderStausEnum;
    // }
    // }
    // return null;
    // }

    }


    public class ProductInfo
    {
    /**
    * 上架下架
    * 注意是公开方法
    * @return
    */
    @JsonIgnore //返回json格式的字符串时,忽略该字段/方法 也就是不序列化
    public ProductStatusEnum getProductStatusEnum(){
    return EnumUtil.getByCode(productStatus,ProductStatusEnum.class);
    }
    }
    public interface CodeEnum<T> {
    T getCode();
    }

    public class EnumUtil {
    /**
    * 通过code和枚举类型获取枚举
    * @param code code
    * @param enumClass 枚举类型class
    * @param <T> 枚举类型
    * @return
    */
    public static <T extends CodeEnum> T getByCode(Integer code, Class<T> enumClass){
    for(T each: enumClass.getEnumConstants()){//遍历枚举类型
    if(each.getCode().equals(code)){
    return each;
    }
    }
    return null;
    }
    }




    13:配置Url项目名 也就是URl的前缀
    #server:
    # 配置Url项目名 也就是URl的前缀
    # context-path: /sell
    14:vo 包 entity包 dataobject 包 form 包
    15:返回对象实体
    @Data
    //@JsonInclude(JsonInclude.Include.NON_NULL) //为null的属性不返回到前端, 也就是类转json 属性为NULL的不参加序列化
    public class ResultVO<T> implements Serializable{
    private static final long serialVersionUID = 4176441568338187710L;//实现序列化
    /**
    * 错误码
    */
    private Integer code;
    /**
    * 提示信息
    */
    private String msg;
    // private String msg = "";//赋予初始值
    //private List<OrderDetail> orderDetailList = new ArrayList<>();//赋予初始值


    /**
    * 返回的具体内容
    */
    private T data;
    }

    16:chrome 安装jsonview 插件
    17:JsonProperty 会返回json 对应的字段名
    @Data
    public class ProductInfoVO implements Serializable{
    private static final long serialVersionUID = 4177439763246797991L;
    @JsonProperty("id")
    private String productId;
    @JsonProperty("name")
    private String productName;

    }
    18:resultVo 静态方法
    public class ResultVOUtil {


    public static ResultVO success(Object object){
    ResultVO resultVO = new ResultVO();
    resultVO.setData(object);
    resultVO.setCode(0);
    resultVO.setMsg("成功");

    return resultVO;
    }

    public static ResultVO success(){
    return success(null);
    }

    public static ResultVO error(Integer code,String msg){
    ResultVO resultVO = new ResultVO();
    resultVO.setMsg(msg);
    resultVO.setCode(code);
    return resultVO;
    }

    }

    19:异常类
    @Data
    public class SellException extends RuntimeException{

    private Integer code;

    public SellException(ResultEnum resultEnum) {
    super(resultEnum.getMessage());
    this.code = resultEnum.getCode();
    }
    public SellException(Integer code, String defaultMessage) {
    super(defaultMessage);
    this.code=code;
    }
    }

    20:计算总价
    BigDecimal orderAmount = new BigDecimal(BigInteger.ZERO);
    orderAmount=productInfo.getProductPrice()
    .multiply(new BigDecimal(orderDetail.getProductQuantity()))//相乘- 计算出一种商品的总价
    .add(orderAmount);
    21:生成唯一id
    public class KeyUtil {

    /**
    * 生成唯一主键
    * 格式:时间+随机数
    * @return
    */
    public static synchronized String getUniqueKey(){//加一个锁
    Random random = new Random();
    Integer number = random.nextInt(900000) + 100000;//随机六位数
    return System.currentTimeMillis()+String.valueOf(number);
    }
    }
    22:判断list 是否为空,以及复制属性
    if(CollectionUtils.isEmpty(orderDetailList)){//判断orderDetailList为空
    throw new SellException(ResultEnum.ORDERDETAIL_NOT_EXIST);
    }
    if(!StringUtils.isEmpty(productId)){
    ProductInfo productInfo = productInfoService.findOne(productId);
    map.put("productInfo",productInfo);
    }
    BeanUtils.copyProperties(orderDTO,orderMaster);//将orderDTO拷贝到orderMaster

    23:gojson 使用
    Gson gson = new Gson();
    List<OrderDetail> orderDetailList = new ArrayList<>();
    try {
    orderDetailList = gson.fromJson(orderForm.getItems(),
    new TypeToken<List<OrderDetail>>() {
    }.getType());
    }catch (Exception e){
    log.error("[对象转换] 错误,json={}",orderForm.getItems());
    throw new SellException(ResultEnum.PARAM_ERROR);
    }

    <!-- json转换 -->
    <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    </dependency>

    24:date加了JsonSerialize注解的参数
    /**
    * 创建时间
    */
    @JsonSerialize(using = Date2LongSerializer.class)//让时间戳精度在秒
    private Date createTime;
    public class Date2LongSerializer extends JsonSerializer<Date> {//Date类型对应OrderDTO中的时间的属性类型
    @Override
    public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
    jsonGenerator.writeNumber(date.getTime() / 1000);//这个date是加了JsonSerialize注解的参数
    }
    }
    25:为空或者null的时候 序列化忽略
    //Include.Include.ALWAYS 默认
    //Include.NON_DEFAULT 属性为默认值不序列化
    //Include.NON_EMPTY 属性为 空(“”) 或者为 NULL 都不序列化
    //Include.NON_NULL 属性为NULL 不序列化
    //@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) //旧版本的,已弃用
    //@JsonInclude(JsonInclude.Include.NON_NULL)//如果属性为null就不返回到前端去,也就是转json 为NULL不参加序列化
    public class OrderDTO {
    }

    或者配置文件中
    # 为null的属性不序列化成json字符串 - 也就是前端不会看到为null的属性
    #  jackson:
    # default-property-inclusion: non_null

    26:前端页面数据处理
    <strong>${msg!"操作成功"}</strong>
    <input hidden type="text" name="categoryId" value="${(productCategory.categoryType)!''}">
    <#list 1..orderDTOPage.getTotalPages() as index>
    <#if currentPage == index>
    <li class="disabled"><a href="#">${index}</a></li>
    <#elseif (index-currentPage lte 2 && index-currentPage gte -2) || (currentPage lte 2 && index lte 5) || (orderDTOPage.getTotalPages()-currentPage lte 2 && orderDTOPage.getTotalPages()-index lt 5)>
    <li><a href="/seller/order/list?page=${index}&size=${size}">${index}</a></li>
    </#if>
    </#list>
    <#--当前页大于等于总页数-->
    <#if currentPage gte orderDTOPage.getTotalPages()>
    <li class="disabled"><a href="#">下一页</a></li>
    <#else>

    <select name="categoryType" class="form-control">
    <#list productCategoryList as productCategory>
    <#--商品类目存在 (productInfo.categoryType)?? -->
    <option value="${productCategory.categoryType}"
    <#if (productInfo.categoryType)?? && productInfo.categoryType==productCategory.categoryType>
    selected
    </#if>
    >
    ${productCategory.categoryName}
    </option>
    </#list>
    </select>


    27:分布式系统
    1:多节点2:消息通信3:不共享内存
    集群
    1:多节点2:不共享内存
    分布式计算
    1:多节点2:消息通信
    28:redis 使用
    <!-- 引入redis依赖 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    redis:
    host: 127.0.0.1
    port: 6379
    password: chenhaoxiang

     * Explain: Redis常量
    */
    public interface RedisConstans {
    /**
    * token前缀
    */
    String TOKEN_PREFIX="token_%s";
    /**
    * 过期时间
    */
    Integer EXPIPE = 7200; //单位s

    }


    @Autowired
    private StringRedisTemplate stringRedisTemplate;


        @GetMapping("/login")
    public ModelAndView login(@RequestParam("openid") String openid,
    HttpServletResponse response,
    Map<String,Object> map){
    //1.openid去和数据库里的数据匹配
    SellerInfo sellerInfo = sellerService.findSellerInfoByOpenid(openid);
    //TODO 未考虑新增的情况,也就是用户微信扫码登录不存在openid的情况下
    if(sellerInfo==null){
    map.put("msg", ResultEnum.LOGIN_FAIL.getMessage());
    map.put("url","/seller/order/list");
    return new ModelAndView("common/error",map);
    }
    //2.设置token至Redis
    // stringRedisTemplate.opsForValue().set("abc","122");//操作某些value 写入key-value
    String token= UUID.randomUUID().toString();
    stringRedisTemplate.opsForValue().set(String.format(RedisConstans.TOKEN_PREFIX,token),openid,RedisConstans.EXPIPE, TimeUnit.SECONDS);//key,value,过期时间,时间单位 s

    //3.设置token至cookie
    CookieUtil.set(response, CookieConstant.TOKEN,token,CookieConstant.EXPIPE);
    //做一个跳转获取订单列表后再跳转 重定向不要带项目名 - 最好带绝对地址 也就是带http://的绝对地址
    return new ModelAndView("redirect:"+projectUrlConfig.getProject()+"/seller/order/list");
    }


    @GetMapping("/logout")
    public ModelAndView logout(HttpServletRequest request,
    HttpServletResponse response,
    Map<String,Object> map){
    //1.从Cookie查询
    Cookie cookie =CookieUtil.get(request,CookieConstant.TOKEN);
    if(cookie!=null){
    //2.清除redis
    stringRedisTemplate.opsForValue().getOperations().delete(String.format(RedisConstans.TOKEN_PREFIX,cookie.getValue()));
    //3.清除cookie
    CookieUtil.del(response,CookieConstant.TOKEN);
    }
    map.put("msg",ResultEnum.LOGOUT_SUCCESS.getMessage());
    map.put("url","/seller/order/list");
    return new ModelAndView("common/success",map);
    }




    29:cookie 相关操作类
    public class CookieUtil {

    /**
    * 清除cookie
    * @param response
    * @param name
    */
    public static void del(HttpServletResponse response,
    String name){
    set(response,name,null,0);
    }

    /**
    * 设置cookie
    * @param response
    * @param name
    * @param value
    * @param maxAge
    */
    public static void set(HttpServletResponse response,
    String name,
    String value,
    int maxAge){
    Cookie cookie = new Cookie(name,value);
    cookie.setPath("/");
    cookie.setMaxAge(maxAge);
    response.addCookie(cookie);
    }

    /**
    * 获取Cookie
    * @param request
    * @param name
    * @return
    */
    public static Cookie get(HttpServletRequest request,
    String name){
    Map<String,Cookie> cookieMap =readCookieMap(request);
    if(cookieMap.containsKey(name)){
    return cookieMap.get(name);
    }
    return null;
    }

    /**
    * 将cookie数组封装为Map
    * @param request
    * @return
    */
    private static Map<String,Cookie> readCookieMap(HttpServletRequest request){
    Map<String,Cookie> cookieMap = new HashMap<>();
    Cookie[] cookies =request.getCookies();
    if(cookies!=null){
    for(Cookie cookie:cookies){
    cookieMap.put(cookie.getName(),cookie);
    }
    }
    return cookieMap;
    }

    }

    30:加入权限切面
    @Aspect
    @Component
    @Slf4j
    public class SellerAuthorizeAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Pointcut("execution(public * com.imooc.controller.Seller*.*(..))" +
    "&& !execution(public * com.imooc.controller.SellerUserController.*(..))")
    public void verify() {}

    @Before("verify()")
    public void doVerify() {
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();

    //查询cookie
    Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
    if (cookie == null) {
    log.warn("【登录校验】Cookie中查不到token");
    throw new SellerAuthorizeException();
    }

    //去redis里查询
    String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
    if (StringUtils.isEmpty(tokenValue)) {
    log.warn("【登录校验】Redis中查不到token");
    throw new SellerAuthorizeException();
    }
    }

    }

    31:拦截系统异常 并进行统一处理

    @ControllerAdvice
    public class SellExceptionHandler {

    @Autowired
    private ProjectUrlConfig projectUrlConfig;
    /**
    * 全局异常捕捉处理
    * 拦截登录异常
    * 重定向至登录页面 - 也就是微信扫码登录
    * @return
    */
    @ExceptionHandler(value = SellAuthorizeException.class)
    public ModelAndView handlerSellerAuthorizeException(){
    return new ModelAndView("redirect:"
    .concat(projectUrlConfig.getWechatOpenAuthorize())//微信开放平台登录授权地址
    .concat("/wechat/qrAuthorize")
    .concat("?returnUrl=")
    .concat(projectUrlConfig.getProject())//服务器访问的地址
    .concat("/seller/login"));
    }

    /**
    * 全局异常捕捉处理
    * @return
    */
    @ExceptionHandler(value = SellException.class)
    @ResponseBody
    public ResultVO handlerSellException(SellException e){
    return ResultVOUtil.error(e.getCode(),e.getMessage());
    }

    /**
    * 全局异常捕捉处理
    * 返回状态码的修改 不再返回200
    * @return
    */
    @ExceptionHandler(value = ResponseBankException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN) //返回状态码的修改
    @ResponseBody
    public ResultVO handlerResponseBankException(ResponseBankException e){
    return ResultVOUtil.error(e.getCode(),e.getMessage());
    }

    }
    32:projectUrlConfig配置类
    @Data
    @ConfigurationProperties(prefix = "projectUrl")
    @Component
    public class ProjectUrlConfig {

    /**
    * 微信公众平台授权url
    */
    public String wechatMpAuthorize;

    /**
    * 微信开放平台授权url
    */
    public String wechatOpenAuthorize;

    /**
    * 点餐系统
    */
    public String sell;
    }
    33:集成mybatis的依赖-
    <!--集成mybatis的依赖-->
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.0</version>
    </dependency>

    34:mybatis配置文件
    @SpringBootApplication
    //@MapperScan(basePackages = {"cn.chenhaoxiang.dataObject.mapper"})//配置mybatis mapper扫描路径
    @EnableCaching //缓存支持 配置Redis缓存需要的
    public class SellApplication {
    public static void main(String[] args) {
    SpringApplication.run(SellApplication.class, args);
    }
    }

    35:mapper相关文件
    //在SellApplication上配置了注解@MapperScan(basePackages = "cn.chenhaoxiang.dataObject.mapper")//配置mybatis mapper扫描路径 所以不用我们再写注解注入Bean
    @Mapper
    @Component //也可以通过上面两个注解实现注入Bean
    public interface ProductCategoryMapper {
    /**
    * 通过Map插入
    * @param map
    * @return
    */
    @Insert("insert into product_category(category_name,category_type) values (#{category_name,jdbcType=VARCHAR},#{category_type,jdbcType=INTEGER})")
    int insertByMap(Map<String,Object> map);

    /**
    * 通过对象插入
    * @param productCategory
    * @return
    */
    @Insert("insert into product_category(category_name,category_type) values (#{categoryName,jdbcType=VARCHAR},#{categoryType,jdbcType=INTEGER})")
    int insertByObject(ProductCategory productCategory);

    /**
    * 通过CategoryType查询
    * @param categoryType
    * @return
    */
    @Select("select * from product_category where category_type=#{categoryType,jdbcType=INTEGER}")
    @Results({
    @Result(column = "category_id",property = "categoryId"),
    @Result(column = "category_name",property = "categoryName"),
    @Result(column = "category_type",property = "categoryType"),
    @Result(column = "create_time",property = "createTime"),
    })//映射
    ProductCategory findByCategoryType(Integer categoryType);

    /**
    * 通过CategoryName查询多条数据
    * @param categoryName
    * @return
    */
    @Select("select * from product_category where category_name=#{categoryName}")
    @Results({
    @Result(column = "category_id",property = "categoryId"),
    @Result(column = "category_name",property = "categoryName"),
    @Result(column = "category_type",property = "categoryType"),
    @Result(column = "create_time",property = "createTime"),
    })//映射
    List<ProductCategory> findByCategoryName(String categoryName);

    /**
    * 通过categoryType修改categoryName
    * @param categoryType
    * @param categoryName
    * @return
    */
    @Update("update product_category set category_name=#{categoryName} where category_type=#{categoryType}")
    int updateByCategoryType(@Param("categoryType")Integer categoryType,@Param("categoryName")String categoryName);

    /**
    * 通过categoryType修改对象category_name
    * @param productCategory
    * @return
    */
    @Update("update product_category set category_name=#{categoryName} where category_type=#{categoryType}")
    int updateByObject(ProductCategory productCategory);

    /**
    * 删除
    * @param categoryType
    * @return
    */
    @Delete("delete from product_category where category_type=#{categoryType}")
    int deleteByCategoryType(Integer categoryType);

    /**
    * 配置xml的方式实现查询
    * 配置文件的resources下面
    * 在yml配置文件中配置xml的扫描路径
    * xml的写法可以百度一下,spring集成mybatis的,现在很多人的写法都是这种xml的。包括我自己,哈哈,以后的项目会用注解方式了
    * mybatis官方不推荐这种写法噢
    * @param categoryType
    * @return
    */
    ProductCategory selectByCategoryType(Integer categoryType);
    }
    36:日志输出级别-debug看到执行的sql
    #  设置cn.chenhaoxiang包下所有类的日志输出级别-debug能看到执行的sql
    logging:
    level:
    cn.chenhaoxiang: debug

    37:mybatis xml 配置
    mybatis:
    mapper-locations: classpath:mapper/*.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="cn.chenhaoxiang.dataObject.mapper.ProductCategoryMapper">

    <resultMap id="BaseResultMap" type="cn.chenhaoxiang.dataObject.ProductCategory">
    <id column="category_id" property="categoryId" jdbcType="INTEGER"></id>
    <id column="category_name" property="categoryName" jdbcType="VARCHAR"></id>
    <id column="category_type" property="categoryType" jdbcType="INTEGER"></id>
    </resultMap>

    <select id="selectByCategoryType" resultMap="BaseResultMap" parameterType="java.lang.Integer">
    select category_id,category_name,category_type
    from product_category
    where category_type=#{categoryType,jdbcType=INTEGER}
    </select>

    </mapper>

    38:synchronized 锁
    粒度不能控制很细只能在方法上面,只能是单点,不能用来分布式
    redis锁刚好相反

    39 :分布式锁
    @Component
    @Slf4j
    public class RedisLock {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
    * 加锁
    * @param key productId - 商品的唯一标志
    * @param value 当前时间+超时时间
    * @return
    */
    public boolean lock(String key,String value){
    if(stringRedisTemplate.opsForValue().setIfAbsent(key,value)){//对应setnx命令
    //可以成功设置,也就是key不存在
    return true;
    }

    //判断锁超时 - 防止原来的操作异常,没有运行解锁操作 防止死锁
    String currentValue = stringRedisTemplate.opsForValue().get(key);
    //如果锁过期
    if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()){//currentValue不为空且小于当前时间
    //获取上一个锁的时间value
    String oldValue =stringRedisTemplate.opsForValue().getAndSet(key,value);//对应getset,如果key存在

    //假设两个线程同时进来,key被占用了。获取的值currentValue=A(get取的旧的值肯定是一样的),两个线程的value都是B,key都是K.锁时间已经过期了。
    //而这里面的getAndSet一次只会一个执行,也就是一个执行之后,上一个的value已经变成了B。只有一个线程获取的上一个值会是A,另一个线程拿到的值是B。
    if(!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue) ){
    //oldValue不为空且oldValue等于currentValue,也就是校验是不是上个对应的商品时间戳,也是防止并发
    return true;
    }
    }
    return false;
    }


    /**
    * 解锁
    * @param key
    * @param value
    */
    public void unlock(String key,String value){
    try {
    String currentValue = stringRedisTemplate.opsForValue().get(key);
    if(!StringUtils.isEmpty(currentValue) && currentValue.equals(value) ){
    stringRedisTemplate.opsForValue().getOperations().delete(key);//删除key
    }
    } catch (Exception e) {
    log.error("[Redis分布式锁] 解锁出现异常了,{}",e);
    }
    }

    }

    40:加减锁
    public void orderProductMocckDiffUser(String productId) {//解决方法一:synchronized锁方法是可以解决的,但是请求会变慢,请求变慢是正常的。主要是没做到细粒度控制。比如有很多商品的秒杀,但是这个把所有商品的秒杀都锁住了。而且这个只适合单机的情况,不适合集群

    //加锁
    long time = System.currentTimeMillis() + TIMEOUT;
    if(!redisLock.lock(productId,String.valueOf(time))){
    throw new SellException(101,"很抱歉,人太多了,换个姿势再试试~~");
    }

    //1.查询该商品库存,为0则活动结束


    //解锁
    redisLock.unlock(productId,String.valueOf(time));

    }

    41:缓存机制

    @SpringBootApplication
    //@MapperScan(basePackages = {"cn.chenhaoxiang.dataObject.mapper"})//配置mybatis mapper扫描路径
    @EnableCaching //缓存支持 配置Redis缓存需要的
    public class SellApplication {
    public static void main(String[] args) {
    SpringApplication.run(SellApplication.class, args);
    }
    }

    <!-- 缓存的依赖 我们不需要再引入了,在websocket中已经引入了。 ctrl+shift+alt+u 快捷键查询maven包依赖引入关系,ctrl+f搜索-->
    <!--<dependency>-->
    <!--<groupId>org.springframework.boot</groupId>-->
    <!--<artifactId>spring-boot-start-cache</artifactId>-->
    <!--</dependency>-->

    方法缓存
    @GetMapping("list")
    @Cacheable(cacheNames = "product",key = "123",unless = "#result.getCode() != 0")
    //Redis缓存注解 Cacheable第一次访问会访问到方内的内容,方法会返回一个对象,返回对象的时候,会把这个对象存储。下一次访问的时候,不会进去这个方法,直接从redis缓存中拿
    //上面的key的值是可以动态写的@Cacheable(cacheNames = "product",key = "#sellerId") sellerId为方法中的参数名
    //condition判断是否成立的条件 例如key = "#sellerId",condition = "#sellerId.length() > 3" 只有条件成立才会对结果缓存,结果不成立是不缓存的
    //依据结果来判断是否缓存 unless = "#result.getCode() != 0",#result其实就是ResultVO,也就是返回的对象
    //unless(除什么之外,如果不 的意思) 如果=0就缓存,需要写成!=0。理解起来就是,除了不等于0的情况之外,才缓存,也就是等于0才缓存。
    //其实就是,你想要什么条件下缓存,你反过来写就行了
    //测试缓存的话,你可以在方法内打一个断点进行测试
    //注意,返回的缓存对象一定要实现序列化!!!
    public ResultVO list(){
    }

    ResultVO 必须实现序列化
    @Data
    //@JsonInclude(JsonInclude.Include.NON_NULL) //为null的属性不返回到前端, 也就是类转json 属性为NULL的不参加序列化
    public class ResultVO<T> implements Serializable{
    private static final long serialVersionUID = 4176441568338187710L;//实现序列化
    }

    42:@CachePut是更新缓存
    //    @CachePut(key = "123") //和上面findOne的返回对象对应
    // @CachePut(cacheNames = "product",key = "123") //和上面findOne的返回对象对应
    public ProductInfo save(ProductInfo productInfo) {
    return productInfoDao.save(productInfo);
    }

    43:项目部署

    tomcat
    java -jar
    mvn clean package -Dmaven.test.skip=true
    java -jar -Dserver.port=9999 sell.jar
    java -jar -Dserver.port=9999 sell.jar

    java -jar -Dserver.port=9999 -Dspring.profiles.active=prod sell.jar
    linux后台运行发布:nohup java -jar jar包名 > /dev/null 2>&1 &

    linux查看进程: ps -ef |grep sell.jar 

    bash 命令
    vim start.sh
    #!/bin/sh
    nohup java -jar sell.jar > /dev/null 2>&1 &

    bash start.sh
    kill -9 2576

    系统重启 服务重启

    cd /etc/systemd/system
    vim sell.service


    systemctl daemon-reload
    systemctl start sell

    systemctl stop sell

    systemctl enable sell

    systemctl disable sell

     



    <build>
    <!--打成包的包名
    启动命令和指定端口: java -jar jar包名 --server.port=8090
    多个配置文件,指定配置文件 --spring.profiles.active=prod
    linux后台运行发布:nohup java -jar jar包名 > /dev/null 2>&1 &
    linux查看进程: ps -ef |grep jar包名

    -->
    <finalName>sell</finalName>




  • 相关阅读:
    用mescroll实现无限上拉增加数据,下拉刷新数据 (学习笔记)
    jackson使用问题:mapper.readValue()将JSON字符串转反序列化为对象失败或异常
    常用 NHibernate.Criterion
    Threading
    并口
    电子称 弹钱箱脉冲
    ZIP文件解压
    wpf 异步加载 只需6段代码
    Newtonsoft.Json使用
    接口post +json +bean
  • 原文地址:https://www.cnblogs.com/zyy1688/p/9804394.html
Copyright © 2020-2023  润新知