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

点菜系统微服务 饭店微信点菜系统_List_02


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>