文章目录
传递参数注解@PathVariable,@RequestParam和RequestBody区别
针对一些非必填的参数,可以使用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 details
:File/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;
}
- 在控制层接收并判断表单信息:
@Valid
和BindingResult
是搭配使用的,多少个表单就要有多少对。
@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
中,通过cookie
中key
是否能对应到redis
数据库中的值判断用户是否已登录。 - 退出登录,则通过
HttpResponse
中存在的cookie
(redis
的key
),删除对应位于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 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
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
:修改数据库url
为jdbc:mysql://ip:port/appName?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
- 项目成功启动后无法访问:
linux
的端口被关闭,不能被外部访问或者防火墙阻挡。解决:1.安装iptables-services;2.打开端口
- 无法连接数据库(