spring-boot(廖师兄微信下单系统)学习笔记

发布时间 2023-08-03 15:43:12作者: 咔咔皮卡丘

1、lombok工具

1.1、依赖 groupId:org.projectlombok;artifactId:lombok
1.2、idea 要安装lombok plugin
1.3、作用: 对model类加一个@Data注解就可以省写set and get方法
对类加@Slf4j注解可以直接通过log调用日志方法
对类加@Getter注解就可以省去写get方法

2、不要在for中有查询数据库行为,会影响性能

3、数据的更新,先查询出来,再通过set方法更新要更新的字段再将整个对象更新

4、@JsonProperty("name")注解VO层bean属性,可以重新指定返回前端的字段名

5、java8,lambda的作用:从对象List中获取某个字段并将其放入集合中

eg:List ageList = personList.stream().map(e->e.getAge()).collect(Collectors.toList());
eg:List contactList = personList.stream().map(e->
new Contact(e.getPhone(),e.getName())
).collect(Collectors.toList());

6、开发中常用到将model中的大部分属性set到vo对象中,可以使用BeanUtils.copyProperties(model,vo);

但要注意,如果model属性的值是null,也会copy过去。

7、判断是否为空:

7.1 集合:CollectionUtils.isEmpty({集合对象})
7.2 字符串:StringUtils.isEmpty({String})

8、controller下方法加参数BindingResult bindingResult, 可以通过bindingResult.hasErrors()判断参数校验是否有误。

9、使用Gson将json字符串转化为List对象:gson.fromJson({String},new TypeToken<{List}>(){}.getType());

10.JsonSerializer的作用:可以实现对对象属性值进行统一重写

用法:10.1 写一个class 继承 JsonSerializer<{对象属性类型}>
10.2 重写serialize方法,在方法中可以对属性值进行处理
10.3 在用到此重写的对象属性上使用注解:@JsonSerialize(using={类.class})

11、Page<{vo}>对象,vo->另外一个vo1的方法:

11.1 将List转成List:可以使用java8,lambda进行转换
11.2 Page <- new PageImpl({List},{Pageable},{List})

12、配置文件中值的获取:

12.1 @ConfigurationProperties(prefix=前缀):将配置文件中的值映射到一个bean的属性(可以为Map<String,String>())中
12.3 类中引用 @Autowired private Environment env; 通过 env.getProperty(“ms.db.username”)可以获取

13、微信微信开发:

13.1 第三方工具SDK:https://github.com/wechat-group/weixin-java-tools
13.2 第三方支付SDK:https://github.com/Pay-Group/best-pay-sdk
13.2 授权时如果提示redirect_uri参数错误,说明配置网页授权获取用户基本信息填写的域名不对

14、www.ibootstrap.cn 可以快速生成前端代码

15、枚举的更高级使用:视频的卖家-订单-controller(下)15:00

16、JSON接口注解:

16.1 @JsonIgnore:如果某个对象的属性不想出现在生成的json中,可以在属性上加上此注解
16.2 @JsonInclude(Include.NON_NULL)如果某个对象的属性值为null时,不想出现在生成的json中,可以在类上加上此注解

17、controller与service层,逻辑分工:如果与用户权限判断相关的逻辑,那就在controller进行,其他逻辑应以操作为单位,写在service层,方便重用

18、freeMarker

18.1 List语法:<#list 1..{num} as index></#list> 可以遍历出1到{num}之间的整数,常用于翻页
18.2 ${}内的变量可以进行运算
18.3 如果修改了模板内容,可以使用build project进行刷新,不需要重启
18.4 ${(值)!””}:意思是如果值拿不到,就显示””,值要加括号
18.5 html标签内可以嵌套freeMarker标签

19、@Bean注解的方法,在其它类中的引用:@Autowired private 方法返回类型 方法名

20、redis:使用时注意一定要设置过期时间

20.1 连接管理工具:redis desktop manager
20.2 依赖:spring-boot-starter-data-redis
20.3 参数配置:spring.redis.host=127.0.0.1;spring.port=6379(直接配置就行了,spring会自动去读取)
20.4 @Autowired private StringRedisTemplate redisTemplate(当然还可以使用其它类型模板)
20.5 redisTemplate.opsForValue().set(key,value,time,TimeUnit.SECOND);
20.6 redisTemplate.opsForValue().getOperations().delete(key);

21、cookie:

21.1 设置:Cookie cookie = new Cookie(key,value);
cookie.setPath("/");
cookie.setMaxAge(7200);
response.addCookie(cookie);
21.2 获取:Cookie[] cookies = request.getCookies();
Cookie cookie1 = null;
for(Cookie cookie:cookies){
    if(cookie.getName().equals({name})){
        cookie1 = cookie;
    }
}
21.3 清除:只要将过期时间设置为0
 

22、ModelAndView:

22.1 重定向时要使用完整的http地址 new ModelAndView("redirect:http://xxxx.xxx.cxx")

23、 aop 

23.1 类加@Aspect注解
23.2 切入点:@Pointcut("execution(public * com.imooc.controller.Seller*.*(..)) && !execution(public * com.imooc.controller.SellerUserController.*(..))")
      public void verify(){}
23.3 进行拦截
@Before("verify()") //指定切入点
public void doVerify(){
    //1、获取requeste
    ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    //2、查询cookie,如果校验不通过就抛出异常(使用统一异常拦截处理)
    //3、查询redis,如果校验不通过就抛出异常(使用统一异常拦截处理)
}

24、拼接

24.1 字符串的拼接可以使用concat({String})方法

25、消息类的异常,只打印日志,不要抛出(不然后影响到正常的逻辑)

26、webSocket服务端:

26.1 引入依赖:spring-boot-starter-websocket
26.2 ServerEndpointExporter注入ioc容器:
@Component
public class WebSocketConfig{
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}
26.3 WebSocket
@Component
@ServerEndpoint("/WebSocket")
public class WebSocket{
    private Session session;
    private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<>();
 
    @OnOpen
    public void onOpen(Session session){
        this.session = session;
        webSocketSet.add(this);
    }
 
    @OnClose
    public void onClose(){        
        webSocketSet.remove(this);
    }
 
    @OnMessage
    public void onMessage(String message){        
        System.out.println("收到客户端发来的消息:"+message);
    }
 
 
    public void sendMessage(String message){
        for(WebSocket  webSocketSet : webSocketSet){
            try{
                webSocket.sesion.getBasicRemote().sendText(message);
                }catch(Exception e){
                    e.printStackTrace();
 
                }
 
        }            
    }
}

27、异常使用心得:

27.1 根据不同业务定义对应的异常(要继承 RuntimeException),行参类型使用枚举
27.2 有异常直接抛出
27.3 处理:对于返回json的接口,使用@ControllerAdice,@ExceptionHandler(value=定义好的异常类.class)
对于返回页面的路由,使用try{}catch(){}的方法进行捕获

28、mybatis的使用:

28.1 引入依赖:mybatis-spring-boot-starter(1.2.0)
28.2 在主启动类中添加@MapperScan(basePackages="com.imooc.dataobject.mapper")//mapper接口所在的包
28.3 在com.imooc.dataobject.mapper包下创建接口类
28.4 设置日志级别为:trace
logging.level.com.imooc.dataobject.mapper = trace//否则就无法看到正确查询时的sql语句
 
28.5.1 map方式写入
@Insert("insert into product_category(category_name,category_type) values(#{categoryName,jdbcType=VARCHAR},#{categoryType,jdbcType=INTEGER})")
int insertByMap(Map<String,Object>);
28.5.2 对象方式写入
@Insert("insert into product_category(category_name,category_type) values(#{categoryName},#{categoryType})")
int insertByObject(ProductCategory productCategory);
28.5.3 查询
@Select("select * form product_category where category_type = #{categoryType}")
@Results({
    @Result(colum = "category_id",property= "categoryId"),
    @Result(colum = "category_name",property= "categoryName"),
    @Result(colum = "category_type",property= "categoryType")
    })
ProductCategory findBycategoryType(Integer categoryType);
28.5.4 某些字段更新
@Update("update product_category set category_name =#{categoryName} where category_type=#{categoryType}")
int updateByCategoryType(@Parm("categoryName") String categoryName,@Parm("categoryType")Integer categoryType );
28.5.5 对象更新
@Update("update product_category set category_name =#{categoryName} where category_type=#{categoryType}")
int updateByObject(ProductCategory productCategory );
28.5.6 删除
@Delete("delete from product_category  where category_type=#{categoryType}")
int deleteByCategoryType(Integer categoryType);
28.6 xml例子:
28.6.1 在resources 下建一个mapper包,再在mapper下建xml文件
28.6.2 配置mybatis.mapper-locations=classpath:mapper/*.xml

29、JPA 与 MyBatis都要注意:

29.1 建表时用sql,不用JPA建表
29.2 表与表之间的关系使用软关联,慎用@oneToMany和@ManyToOne

30、压测工具:Apache ab 

30.1 ab -n 100 -c 100 网址 (-n 100 表示 100 个请求,-c 100 表示 100 个并发)
30.2 ab -t 60 -c 100 网址 (-t 60 表示 60秒内不停地请求,-c 100 表示 100 个并发)

31、并发:

31.1 使用Synchronized 可以保证数据线程安全,但速度会慢,无法做到细粒度控制,只适合单点的情况
31.2 使用redis分布式锁(因为redis是单线程的):用到的命令:SETNX(key已存在就什么也不做,若key不存在就更新),GETSET(先取出值,将更新)
31.2.1 加锁/解锁方法:
@Component
@Slf4j
public class RedisLock {
    @Autowired
    private StringRedisTemplate redisTemplate;
 
    /**
     * 加锁
     * @param key
     * @param value 当前时间+超时时间
     * @return
     */
    public  boolean lock(String key,String value){
        if(redisTemplate.opsForValue().setIfAbsent(key,value)){
            return true;
        }
        //currentValue =A ,两个线程的value都是B,其中一个线程拿到锁
        String currentValue = redisTemplate.opsForValue().get(key);
        //如果锁过期
        if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue)<System.currentTimeMillis()){
            //获取上一个锁的时间
            String oldValue = redisTemplate.opsForValue().getAndSet(key,value);
            if(!StringUtils.isEmpty(oldValue) &&oldValue.equals(currentValue)){
                return true;
            }
        }
        return false;
    }
 
    public void unlock(String key,String value){
        try{
            String currentValue = redisTemplate.opsForValue().get(key);
            if(!StringUtils.isEmpty(currentValue) && currentValue.equals(value)){
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        }catch (Exception e){
            log.error("【redis分布式锁】解锁异常,{}",e.getMessage());
        }
    }
}

32、缓存:

32.1 启动类加@EnableCaching//如果不存在,就引入依赖spring-boot-starter-cache
32.2 对要使用缓存的方法加@Cacheable(cacheNames="product",key="123")
32.2.1 属性cacheNames:缓存key的前缀
32.2.2 属性key:缓存key,可以动态赋值key="#参数",eg:key = "#productId"
32.2.3 属性condition:条件判断,成立时才缓存,eg:condition = "#productId.length()&10"
32.2.4 unless:条件,如果不,eg:unless = "#result.getCode!=0)//只有在返回结果的code为0时才进行缓存,#result可以代替方法的返回对象
32.3 对触发更新缓存的方法加@CachePut(cacheNames="product",key="123")//会将key相同的(包括前缀)的缓存值更新(要求方法的返回对象与使用缓存的方法的返回对象相同)
32.4 对触发清空缓存的方法加@CacheEvict(cacheNames="product",key="123")//会将key相同的(包括前缀)的缓存清空(方法的返回对象可以与使用缓存的方法的返回对象不同)
32.5 对类加@CacheConfig(cacheNames="product")//这个类的方法如果使用缓存,则前缀都为product
32.6 注意:要缓存的对象一定继承可序列化类

33、项目部署

33.1 多环境配置:application-dev.yml application-prod.yml  application.yml(spring.profiles.active=dev)//默认是选择dev
33.2 打包 mvn clean packsge -Dmaven.test.skip=true
33.3 临时启动 java -jar -Dserver.port=8080 -Dspring.profiles.active=prod sell.jar
33.4 后台启动 nohup java -jar -Dserver.port=8080 -Dspring.profiles.active=prod sell.jar && /data/logs/didafenqi/didafenqi-api/log.out &
33.5 service方式启动
33.5.1内容:sell.service(放在/etc/systemd/system/下)
[Unit]
Description=sell
After=syslog.target network.target
 
[Service]
Type=StringRedisTemplate
 
ExecStart=/usr/bin/java -jar -Dserver.port=8080 -Dspring.profiles.active=prod sell.jar
ExecStop=/bin/kill -15 $MAINPID
 
User=root
Group=root
 
[Install]
WantedBy=multi-user.target
 
33.5.2启动命令 systemctl start sell
33.5.3关闭命令 systemctl stop sell
33.5.4开机启动 systemctl enable sell
33.5.5取消开机启动 systemctl disable sell