Springboot笔记---持续更新中

传递参数注解@PathVariable,@RequestParam和RequestBody区别

image
(markdown引用图片方法BASE64编码)

针对一些非必填的参数,可以使用required关键字来标识,同时必须设置默认值defaultValue,如对price参数的获取:

@RequestParam(value = "price",required = false,defaultValue = "0") Integer price

springboot注解及相关知识

- @SpringBootApplication(scanHasePackage={"包名"}):包扫描,扫描controller、service、dao等。
- @EnableAutoConfiguration:开启springboot默认配置
- @Controller:控制层注解
- @Service:业务层注解
- @Repository:持久层注解
- @RestController:控制层注解,且返回值为`JSON`格式
- @RequestMapping("映射地址"): Controller中action的映射地址
- @RequestBody:用于读取Request请求的body部分数据,把相应的数据绑定到要返回的对象上;
- @ResponseBody: 用于将Controller的方法返回的对象,返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用。
- @ControllerAdvice,@RestControllerAdvice配合@ExceptionHandler注解可以统一处理异常。
- @Aspect:切面编程,定义某个类为切面容器;配合@Pointcut(必须定义在public void型方法上,比如构造类)定义切面;@Around、@Before、@After定义执行方法中、前、后再执行。可用于日志记录。
- @PropertySource():指定读取的配置文件位置  定义在类上
- @Value:注入配置项的值
- @Configuration:表示该类是一个配置类
- @Component:是一个类级别上的注解,表明一个类会作为组件类,并告知Spring要为这个类创建bean。
- @Bean是一个方法级别上的注解,主要用在@Configuration注解的类里,也可以用在@Component注解的类里。添加的bean的id为方法名
- @Transactional(rollbackFor=Exception.class):事务注解,只要抛出异常就回滚
- @Primary: 当某个接口返回多个相同实现类实体时,指定优先考虑装配哪个实体
- @Qualifier:指定装配的bean的名称;与@Autowired自动装配bean搭配使用。它的作用是在按照类型注入的基础之上,再按照 Bean 的 id 注入。所以如果是使用了 @Autowire 注解自动注入,但是容器中却有多个匹配的实例,可以搭配此注解,指定需要注入的实例 id。
- @Autowired: 此注解自动按照类型注入。从容器中寻找符合依赖类型的实例,当使用该注解注入属性时,set
方法可以省略。但是因为按照类型匹配,如果容器中有多个匹配的类型,会抛出异常,需要指定引入的实例 id。如果找不到匹配的实例,那么也会抛出异常。
- @Resource:指定类型注入还是id注入;    
    @Resource   //默认按照 id 为 userDao的bean实例注入    
    @Resource(name="userDao")  //按照 id 为 userDao的bean实例注入    
    @Resource(type="UserDao")  //按照 类型 为 UserDao的bean实例注入    
    



配置文件application.properties

#springboot内置了tomcat
server.port=8080  #修改tomcat端口号

#配置视图解析器(前后缀) 如return "/pages/front/home.jsp"页面跳转
spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.jsp
#配置之后 如return "front/home"页面跳转

Springboot跨域实现

cors跨域:
跨域实际上源自浏览器的同源策略,所谓同源,指的是协议、域名、端口都相同的源 (域)。浏览器会阻止一个域的 JavaScript 脚本向另一个不同的域发出的请求,这也是为了保护浏览器的安全。

@Configuration//配置类
public class CorsConfig {
	@Bean
	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/**")//对所有请求路径
				          .allowedOrigins("*")//允许所有域名
				          .allowCredentials(true)//允许cookie等凭证
				          .allowedMethods("GET", "POST", "DELETE", "PUT","PATCH")//允许所有方法
				          .maxAge(3600);  
			}
		};
	}
}

拦截器设置

spring设置了拦截器,通过实现HandlerInterceptor接口转换为拦截器类,实现pre、post、after方法;其中after方法即使抛出异常也会执行,而post不会。并通过@Component注解将该类交给spring管理。

拦截器注册:在启动类中实现WebMvcConfigurer接口中的addInterceptors

    @Autowired
    private AuthFilter authFilter;//自定义拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authFilter).addPathPatterns("/merchants/*");//定义拦截路径
    }

bug

  • 404页面无法跳转:springboot默认跳转到resources/static文件夹下的静态页面。
  • Table 'passbook.hibernate_sequence' doesn't exist: 将id的生成策略设置成@GeneratedValue(strategy = GenerationType.IDENTITY)
  • Unable to import maven project: See logs for detailsFile/settings/maven/importing/JDK for importing修改为自己的JDK1.8具体方法链接

表单校验与页面枚举值的显示

  • 表单校验采用Hibernate的校验机制。
  • 接收表单,应为其创建相应的表单对象;并通过相关注解限制字段。
  • 注意:@NotEmpty注解只能对字符串进行限制,不能限制数值类型;数值类型只能用@NotNull判空。
@Data
public class ProductForm{

    private String productId;

    @NotEmpty(message = "商品名称不能为空")
    @Size(max = 15,min = 1,message = "商品名称长度在1-15个字符之内")
    private String productName;

    @NotNull(message = "商品价格不能为空")
    @DecimalMin(value = "0",message = "商品价格形式不正确")
    private BigDecimal productPrice;

    @NotNull(message = "商品库存不能为空")
    private Integer productStock;

    /*
    商品图片的链接地址
     */
    @NotEmpty(message = "商品图片不能为空")
    private String productIcon;
}
  • 在控制层接收并判断表单信息:@ValidBindingResult是搭配使用的,多少个表单就要有多少对。
@PostMapping("/addOrUpdate")
    public String addOrUpdate(@Valid ProductForm productForm,
                      BindingResult bindingResult,
                      Model model){
        try{
            //判断表单中是否存在错误
            if(bindingResult.hasErrors()){
                model.addAttribute("formError",bindingResult.getFieldError().getDefaultMessage());
                return addOrUpdateUI(productForm.getProductId(),model);
            }

            if(StringUtils.isEmpty(productForm.getProductId())){
                //添加操作
                ProductInfo productInfo=new ProductInfo();
                BeanUtils.copyProperties(productForm,productInfo);
                productInfoService.saveOne(productInfo);
                model.addAttribute("msg","添加成功");
            }else{
                ProductInfo productInfo=productInfoService.selectProductById(productForm.getProductId());
                BeanUtils.copyProperties(productForm,productInfo);
                productInfoService.saveOne(productInfo);
                model.addAttribute("msg","修改成功");
            }

            return list(1,1,model);

        }catch (Exception e){
            model.addAttribute("errorMsg",e.getMessage());
            model.addAttribute("errorUrl","/sell/sellOrder/view");
            return "/common/error";
        }
    }
  • 页面视图与枚举类值的转换:在页面展示枚举类的信息时需根据枚举类的编码值显示相应的内容。如性别枚举类,若编码为1则表示男,为0则表示女等。
    • 为简化操作,可在显示时先将信息转化输出;
/**
 * 页面输出枚举信息
 */
public interface PageEnum<T>{

    T getCode();//实现该接口必须有getCode()方法。
}

/**
 * 支付状态
 */
public enum PayStatus implements PageEnum{//可能有多个枚举类,实现同一个接口

    WAIT_PAY(0,"待支付"),
    SUCCESS_PAY(1,"已支付");

    @Getter
    private Integer code;

    @Getter
    private String message;

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

}

/**
 * 工具方法:获取某个枚举类
 */
public class GetPageEnum {

    public static <T extends PageEnum> T getPageEnum(Integer code,Class<T> enumClass){
        //遍历相应枚举类的实例
        for(T each:enumClass.getEnumConstants()){
            if(code.equals(each.getCode())){
                return each;
            }
        }
        return null;
    }

}

//在相应的model中增加获取枚举类消息的方法,方便页面获取
public String getPayStatusEnumMsg(){

        return GetPageEnum.getPageEnum(payStatus,PayStatus.class).getMessage();
    }


日志设置

  • 日志:“谁在何时何地做了何事”—定义日志对象。
  • “何事”:定义用户的动作,“有各种事”。
  • 提供创建日志对象的工具类。

Spring Boot 默认已经集成了日志功能,使用的是 logback 开源日志系统。

系统配置文件:

# 设置日志级别 包括ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 等级别日志信息。
logging.level.root=WARN
# root 可以改为指定包名或类名,表示设置指定包或者类的日志级别。
#指定日志文件
logging.file=C:\\logs\\spring-boot-log.log
# 设置日志目录
logging.file.path=C:\\logs
#如果同时配置了 logging.file 和 logging.file.path ,则只有 logging.file 生效。

在类中使用:

private Logger logger = LoggerFactory.getLogger(this.getClass());
logger.info("info");

自定义日志配置

# 指定logback配置文件,位于resources目录下
logging.config=classpath:logback-spring.xml

logback-spring.xml文件示例:

<?xml version="1.0" encoding="UTF-8"?>
<!-- logback 配置 -->
<configuration>
	<!-- 输出到控制台 -->
	<appender name="STDOUT"
		class="ch.qos.logback.core.ConsoleAppender">
		<encoder
			class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
			<!--格式化输出:%d表示日期;%thread表示线程名;%-5level:左对齐并固定显示5个字符;%msg:日志消息;%n:换行符; -->
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -
				%msg%n</pattern>
		</encoder>
	</appender>
	<!-- 输出到文件 -->
	<appender name="FILE"
		class="ch.qos.logback.core.rolling.RollingFileAppender">
		<!-- 正在打印的日志文件 -->
		<File>C:/logs/spring-boot-log.log</File>
		<encoder>
			<!--格式化输出:%d表示日期;%thread表示线程名;%-5level:左对齐并固定显示5个字符;%msg:日志消息;%n:换行符; -->
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -
				%msg%n
			</pattern>
		</encoder>
		<!-- 日志文件的滚动策略 -->
		<rollingPolicy
			class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!-- 日志归档 -->
			<fileNamePattern>C:/logs/spring-boot-log-%d{yyyy-MM-dd}.log
			</fileNamePattern>
			<!-- 保留30天日志 -->
			<maxHistory>30</maxHistory>
		</rollingPolicy>
	</appender>
	<!-- 指定日志输出的级别 -->
	<root level="INFO">
		<appender-ref ref="STDOUT" />
		<appender-ref ref="FILE" />
	</root>
</configuration>

Spring Task定时任务

Spring Task 是 Spring Boot 内置的定时任务模块。

1.@EnableScheduling:开启定时任务功能,一般在启动类上使用。作用是发现注解 @Scheduled的任务并由后台执行
2.新建 MySpringTask 任务类,添加 @Component 注解注册 Spring 组件,定时任务方法需要在 Spring 组件类才能生效。
3.类中方法添加了 @Scheduled 注解,定义定时的时间,支持cron表达式。

Cron 表达式并不难理解,从左到右一共 6 个位置,分别代表秒、分、时、日、月、星期,以秒为例:

如果该位置上是 0 ,表示在第 0 秒执行;
如果该位置上是 * ,表示每秒都会执行;
如果该位置上是 ? ,表示该位置的取值不影响定时任务,由于月份中的日和星期可能会发生意义冲突,所以日、 星期中需要有一个配置为 ? 。
按照上面的理解,cron = "0 * * * * ?" 表示在每分钟的 00 秒执行、cron = "0 0 0 * * ?" 表示在每天的 00:00:00 执行。

4.通过看 @Scheduled源码可以看出它支持多种参数:
    (1)cron:cron表达式,指定任务在特定时间执行;
    (2)fixedDelay:表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms;
    (3)fixedDelayString:与fixedDelay含义一样,只是参数类型变为String;
    (4)fixedRate:表示按一定的频率执行任务,参数类型为long,单位ms;
    (5)fixedRateString: 与fixedRate的含义一样,只是将参数类型变为String;
    (6)initialDelay:表示延迟多久再第一次执行任务,参数类型为long,单位ms;
    (7)initialDelayString:与initialDelay的含义一样,只是将参数类型变为String;
    (8)zone:时区,默认为当前时区,一般没有用到。

此时所有的定时任务都是在同一个线程池用同一个线程来处理的,那么我们如何来并发的处理各定时任务呢…

1.在启动类上加@EnableAsync开启多线程
2.调用的方法上加上@Async使用多线程
3.配置连接池
@Configuration
public class ScheduleConfiguration implements SchedulingConfigurer {


    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(this.getTaskScheduler());
    }

    private ThreadPoolTaskScheduler getTaskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(20);
        taskScheduler.setThreadNamePrefix("schedule-pool-");
        taskScheduler.initialize();
        return taskScheduler;
    }
}
  • Quartz 定时任务需要通过 Job 、 Trigger 、 JobDetail 来设置。

Job:具体任务操作类
Trigger:触发器,设定执行任务的时间
JobDetail:指定触发器执行的具体任务类及方法
Quartz与Spring task相比,quartz框架可以动态配置定时时间。

Spring AOP切面

在访问某个路径之前或之后或访问中时,进行额外的操作。
即横向切入方法中,对方法进行额外操作。

1.引入jar

	<!-- AOP -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

2.定义切面处理类

使用到的注解

@Aspect:表示该类为切面处理类
@Component:将切面类注入到Ioc容器
@Poincut:定义切入点,放在处理方法上
@Before:进入切面方法之前执行;类似的有@After,@Around

3.示例

@Aspect
@Component
public class AopHandler {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("within(springboot.study.Controller..*)")
    void daoAspect(){
    }

    @Around("daoAspect()")
    public void handleDaoAspect(ProceedingJoinPoint joinPoint){
        System.out.println("--------------------- ajajajajajajajaja----------");
        logger.info("controller方法:{},时间为:{} come in aspect",joinPoint.getSignature(),new Date());
    }

}

异常定义与处理

  • Java异常类基础知识
  • 程序中一般对运行时异常(RuntimeException)进行捕获处理。
  • 自定义异常类:继承RuntimeException类,提供构造方法,通过调用父类的构造方法创建实例super(message)
public class OrderException extends RuntimeException {

    /**
     * 异常码
     */
    @Getter
    private Integer code;

    public OrderException(OrderExceptionEnum orderExceptionEnum){
        super(orderExceptionEnum.getMessage());
        this.code=orderExceptionEnum.getCode();
    }

    public OrderException(Integer code,String message){
        super(message);
        this.code=code;
    }

}

"定义异常类枚举"

/**
 * 订单异常枚举类定义
 */
public enum OrderExceptionEnum {

    PRODUCT_NOT_EXIST(1,"商品不存在"),
    PRODUCT_SHORTAGE(2,"库存不足"),

    PRODUCT_ORDER_NOT_EXIST(3,"订单不存在"),
    PRODUCT_ORDERITEM_NOT_EXIST(4,"订单项不存在"),
    ORDER_CANCLE_WRONG(5,"订单已完结或已取消"),
    ORDER_CANCLE_FAIL(6,"订单取消失败"),
    ORDER_STATUS_WRONG(7,"订单状态错误"),
    ORDER_FINISH_FAIL(8,"订单完结失败"),
    ORDER_PAY_FAIL(9,"订单支付失败"),
    PAY_STATUS_WRONG(10,"支付状态不正确"),
    ORDER_PARAM_WRONG(11,"订单参数错误")
    ;
    @Getter
    private Integer code;
    @Getter
    private String message;

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

}
  • 通过ControllerAdvice处理异常。
@ControllerAdvice
public class HandlerException {

    @ExceptionHandler(SellerException.class)//需要捕获的异常类
    public String handleLoginException(Exception e){
        if(e instanceof SellerException){
            e.printStackTrace();
            return "login";
        }
        return "";
    }


}

页面与后端值传递方法

技术springboot+freemarker

  • 页面跳转:
@Controller
@RequestMapping("/product")
public class ProductController {

    @GetMapping("/list")
    public String list(@RequestParam(value = "pagenum",defaultValue = "1")Integer pagenum,
                       @RequestParam(value = "size",defaultValue = "1")Integer size,
                       Model model){
        PageRequest pageRequest=PageRequest.of(pagenum-1,size);
        Page<ProductInfo> productInfoPage = productInfoService.findAll(pageRequest);
        model.addAttribute("currentPage",pagenum);
        model.addAttribute("modelPage",productInfoPage);

        return "product/list";
    }
}

1.@Controller:跳转到页面时使用
2.返回的字符串表示页面(freemarker)的路径:默认在resources/templates下;这里表示跳转到templates文件夹下
的product文件夹下的list.ftl页面。若要重定向跳转则return "redirect:product/list"。
3.Model为页面与逻辑代码传值的中间对象,在页面通过${属性名}取出值。

  • 向页面返回Restful风格的数据:
{
    code:200,
    message:"OK",
    data:{
        name:"hello"
    }
}

1.定义返回对象
/*
    前端数据,JSON格式,先存储为对象后通过注解转化为JSON数据
 */
@Data
public class ResultVO <T> {
    /*
    错误码
     */
    private Integer code;

    private String msg;

    private T data;

}

2. 统一返回的对象

/**
 * 返回请求结果的包装类
 */
public class ResultWrapper {

    public static ResultVO success(Object data){
        ResultVO resultVO=new ResultVO();
        resultVO.setCode(JsonMsg.SUCCESS.getCode());//定义枚举类
        resultVO.setMsg(JsonMsg.SUCCESS.getMsg());
        resultVO.setData(data);

        return resultVO;
    }

    public static ResultVO success(){
        ResultVO resultVO=new ResultVO();
        resultVO.setCode(JsonMsg.SUCCESS.getCode());
        resultVO.setMsg(JsonMsg.SUCCESS.getMsg());
        return resultVO;
    }

    public static ResultVO error(){
        ResultVO resultVO=new ResultVO();
        resultVO.setCode(JsonMsg.FAILURE.getCode());
        resultVO.setMsg(JsonMsg.FAILURE.getMsg());
        return resultVO;
    }

}

3.控制器返回对象

@RestController//JSON返回
@RequestMapping("/buyOrder")
@Slf4j
public class BuyOrderController {

    //查看订单详情
    @GetMapping("/view_detail/{id}")
    public ResultVO viewOrderItems(@PathVariable("id") String orderId){

        OrderVO orderVO=productOrderService.findByOrderId(orderId);

        return ResultWrapper.success(orderVO.getOrderItemList());

    }

}

Apache Bench(ab)测压

  • 介绍:模拟高并发的环境,测试软件在高并发环境下的准确性和效率。
  • 由于在windows上安装成功但不能运行,推荐在linux上安装。
  • linux终端输入yum -y install httpd-tools安装。安装成功后通过ab -v查看版本。ab -help查看所有命令。
  • ab -c 100 -n 1000 http://baidu.com/:表示创建100个并发进程,同时发送请求给百度地址,总共1000个请求。
  • 具体介绍
  • 更多工具:Jmeter图形化测压工具,且支持带参数请求。

redis存储与登录拦截

  • pom.xml文件中引入spring-boot-starter-data-redis文件
  • application.properties中配置redis相关信息,包括端口号(默认6379)、主机ip和密码。
  • 通过StringRedisTemplate类(RedisTemplate的子类,此处是操作字符串类型的redis)对redis数据库进行操作。
  • 用户登录后将其唯一id与自动生成的key存入redis数据库(key-value形式)。注意设置过期时间(很重要)。
  • 同时将key设置到cookie中,通过cookiekey是否能对应到redis数据库中的值判断用户是否已登录。
  • 退出登录,则通过HttpResponse中存在的cookierediskey),删除对应位于redis中的数据记录;并同时将该cookie的过期时间(maxAge)设置为0;即删除此cookie
    @Autowired
    private StringRedisTemplate stringRedisTemplate;//redis操作类

    @PostMapping("/login")
    public String login(@RequestParam String openid,
                        HttpServletResponse response,
                        Model model){

        //根据openid查询mysql数据库中是否存在该用户
        Seller seller=sellerService.findOneByOpenid(openid);

        if(null==seller){
            return "login";//登录失败
        }

        //若存在,将用户Openid存入redis数据库,并设置过期时间
        String token_key= UUID.randomUUID().toString();
        Integer expire= RedisConstant.EXPIRE;//过期时间使用枚举

        //存入redis数据库
        stringRedisTemplate.opsForValue().set(RedisConstant.KEY_PREFIX+token_key,openid,expire, TimeUnit.SECONDS);

        //将redis数据库中对应的用户openid和key信息设置为token
        Cookie cookie=new Cookie(CookieConstant.TOKEN_KEY,RedisConstant.KEY_PREFIX+token_key);
        cookie.setMaxAge(RedisConstant.EXPIRE);
        cookie.setPath("/");
        response.addCookie(cookie);
        return "redirect:/sellOrder/view";
    }

Synchronized锁和Redis分布式锁

  • synchronized关键字,修饰在方法上,可以实现并发线程的单线程访问;但效率低且是粗粒度型的锁。

redis缓存

  • springboot+redis
  • @Cacheable(name="",key=""):表示将该对象以name+key为键存入redis数据库。
  • @CacheEvict(name="",key=""):表示将以name+key为键的记录删除,以更新缓存,使得缓存数据与数据库数据一致。
  • @CacheConfig(name=""):注解在类上,表示统一指定缓存的name字段。
springboot中使用redis
1.pom引入依赖
      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
2.注入RedisTemplate对象
     @Autowired
    private RedisTemplate<String,String> redisTemplate;
3.使用
     String value = redisTemplate.opsForValue().get("1");//获取值
     redisTemplate.opsForValue().set("aa","ttt",30,TimeUnit.SECONDS);//设置键值并设置过期时间
       
  • redis穿透
现象:当请求查询的数据不在缓存时,就会查询数据库;而数据库中也查询不到,那么每次查询该数据就会忽略redis缓存
直接查询数据库。当有大量请求恶意攻击时会导致数据库崩溃。
解决办法:每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
  • redis击穿
现象:当某条数据被大量请求时,在失效的瞬间会有大量请求到数据库,导致数据库崩溃。
解决:给大量请求的数据作为热键,设置为永不过期。
  • redis雪崩
现象:请求的数量超过了缓存承受的数量,导致缓存挂掉,请求直接到了数据库,大量的请求也导致数据库挂掉。
解决:
    事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
    事中:本地 ehcache 缓存 + hystrix 限流和降级,避免 MySQL 被打死。
    事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

redis缓存击穿等问题参考

WebSocket和html5

  • webSocket用于页面与逻辑代码的消息传递,且连接是全双工通信。
  • pom.xml文件中引入spring-boot-starter-websocket依赖。
  • 在需要进行消息展示的页面插入以下代码。
  • 一般页面使用modal组件显示消息内容。
<script>

        if('WebSocket' in window){
            
            //链接地址
            var websocket=new WebSocket("ws://localhost:8080/sell/websocket");

            websocket.onopen=function (ev) {
                console.log("建立websocket链接");
            }

            websocket.onclose=function (ev) {
                console.log("连接取消");
            }

            websocket.onmessage=function (ev) {
                console.log("获取信息:" + ev.data);
                $('#messageModal').html(ev.data);
                $('#myModal').modal('show');
                document.getElementById('message_mp3').play();
                console.log("发送消息");
            }

            websocket.onbeforeunload=function (ev) {
                console.log("链接建立之前先关闭之前的链接");
            }

        }else{
            alert("您的浏览器不支持WebSocket");
        }

</script>
  • 在逻辑代码部分,创建接收页面传递过来的请求服务;
/**
 * websocket配置类  注入对象
 */
@Component
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter WebServEndpoint(){
        return new ServerEndpointExporter();
    }
}


/**
 * 消息服务
 */
@Component
@ServerEndpoint("/websocket")//访问路径
public class WebSocketService {

    private Session session;//一次会话

    //用于接收所有的链接请求
    private static CopyOnWriteArraySet<WebSocketService> webSockets=new CopyOnWriteArraySet<WebSocketService>();

    @OnOpen
    public void onOpen(Session session){
        this.session=session;
        webSockets.add(this);//请求接收同时建立链接,将当前WebSocketService加入集合
    }

    @OnClose
    public void onClose(){
        webSockets.remove(this);//取消链接,将当前WebSocketService移出集合
    }

    @OnMessage
    public void onMessage(String message){
        System.out.println(message);//消息获取
    }

    /**
     * 发送消息给页面
     * @param message
     */
    public void sendMessage(String message){
        //广播发送消息
        for(WebSocketService webSocketService:webSockets){
            try {
                webSocketService.session.getBasicRemote().sendText(message);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

}
  • 在需要进行消息通信的地方调用WebSocketService类的sendMessage方法即可。

Linux部署

  • 项目打成Jar包:进入项目文件,通过终端输入命令mvn clean package -Dmaven.test.skip=true(前提已安装maven)。jar包位置在项目的target文件夹下。
  • 可通过pom.xml文件中的<build>标签下通过<final-name>修改项目的最终jar包名。
  • jar包传输到远程服务器:scp target/jar包名 服务器ip:/app表示将target文件夹下的jar包传输到服务器的根目录(/)下的app文件夹下。(app文件夹自建)
  • 登录远程服务器,进入app(jar包所在文件夹);键入java -jar jar包名即可运行项目。
  • 可能出现的bug:
    • 无法连接数据库(mysql8):将配置中的localhost修改为机器的对外ip地址;
    • 用户无权限访问数据库:创建用户并授权。创建用户:create user 'root'@'ip地址' identified by '密码'。给用户授权:grant all privileges on *.* to 'root'@'ip地址'
    • public keys are not allowed:修改数据库urljdbc:mysql://ip:port/appName?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    • 项目成功启动后无法访问:linux的端口被关闭,不能被外部访问或者防火墙阻挡。解决:1.安装iptables-services;2.打开端口
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值