智慧保安后台管理项目备份
Administrator
2022-01-10 63351e58ad05ef72351feb3cc60758a9619a58b3
1. 登录逻辑修改,新增登录记录
2. 登录记录查询导出新增
9 files modified
22 files added
1539 ■■■■■ changed files
pom.xml 5 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/Application.java 4 ●●● patch | view | raw | blame | history
src/main/java/org/springblade/common/config/BladeConfiguration.java 58 ●●●● patch | view | raw | blame | history
src/main/java/org/springblade/common/config/CommonConfig.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/common/config/ServerConfig.java 65 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/FTP/monitor.java 2 ●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/auth/endpoint/BladeTokenEndPoint.java 40 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/loginrecord/controller/LoginRecordController.java 122 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/loginrecord/entity/LoginRecord.java 75 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/loginrecord/excel/LoginRecordExcel.java 61 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/loginrecord/mapper/LoginRecordMapper.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/loginrecord/mapper/LoginRecordMapper.xml 141 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/loginrecord/service/LoginRecordService.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/loginrecord/service/impl/LoginRecordServiceImpl.java 55 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/loginrecord/vo/LoginRecordVo.java 57 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/nettyServer/ChannelMap.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/nettyServer/NettyConfig.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/nettyServer/Server.java 86 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/nettyServer/ServerHandler.java 89 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/runner/MyRunner.java 24 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/system/controller/UserController.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/webscoket/ChannelSupervise.java 38 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/webscoket/WebSocketHandler.java 193 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/webscoket/WebSocketServer.java 59 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/webscoket/controller/PushMsgController.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/webscoket/index.html 57 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/webscoket/service/IPushMsgService.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/webscoket/service/impl/PushMsgServiceImpl.java 32 ●●●●● patch | view | raw | blame | history
src/main/resources/application-dev.yml 5 ●●●●● patch | view | raw | blame | history
src/main/resources/application-test.yml 4 ●●●● patch | view | raw | blame | history
src/main/resources/application.yml 22 ●●●● patch | view | raw | blame | history
pom.xml
@@ -246,6 +246,11 @@
            <version>2.4</version>
            <classifier>jdk15</classifier>
        </dependency>
        <!--netty-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
        </dependency>
    </dependencies>
    <build>
src/main/java/org/springblade/Application.java
@@ -20,7 +20,6 @@
import org.springblade.core.launch.BladeApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
 * 启动器
 *
@@ -28,11 +27,10 @@
 */
@EnableScheduling
@SpringBootApplication
public class Application {
public class Application{
    public static void main(String[] args) {
        BladeApplication.run(CommonConstant.APPLICATION_NAME, Application.class, args);
    }
}
src/main/java/org/springblade/common/config/BladeConfiguration.java
@@ -52,35 +52,35 @@
        secureRegistry.excludePathPatterns("/webjars/**");
        secureRegistry.excludePathPatterns("/swagger-resources/**");
        secureRegistry.excludePathPatterns("/druid/**");
        secureRegistry.excludePathPatterns("/performance/**");
        secureRegistry.excludePathPatterns("/information/**");
        secureRegistry.excludePathPatterns("/member/**");
        secureRegistry.excludePathPatterns("/shareholder/**");
        secureRegistry.excludePathPatterns("/honor/**");
        secureRegistry.excludePathPatterns("/dispatcher/**");
        secureRegistry.excludePathPatterns("/employment/**");
        secureRegistry.excludePathPatterns("/train/**");
        secureRegistry.excludePathPatterns("/examination/**");
        secureRegistry.excludePathPatterns("/experience/**");
        secureRegistry.excludePathPatterns("/jurisdiction/**");
        secureRegistry.excludePathPatterns("/permit/**");
        secureRegistry.excludePathPatterns("/record/**");
        secureRegistry.excludePathPatterns("/revoke/**");
        secureRegistry.excludePathPatterns("/recordk/**");
        secureRegistry.excludePathPatterns("/punish/**");
        secureRegistry.excludePathPatterns("/blade-resource/attach/**");
        secureRegistry.excludePathPatterns("/social/**");
        secureRegistry.excludePathPatterns("/car/**");
        secureRegistry.excludePathPatterns("/gun/**");
        secureRegistry.excludePathPatterns("/equipage/**");
        secureRegistry.excludePathPatterns("/blade-user/**");
        secureRegistry.excludePathPatterns("/trainExam/**");
        secureRegistry.excludePathPatterns("/trainingRegistration/**");
        secureRegistry.excludePathPatterns("/workReport/**");
        secureRegistry.excludePathPatterns("/seinspect/**");
        secureRegistry.excludePathPatterns("/coinspect/**");
        secureRegistry.excludePathPatterns("/dispatcherUnit/**");
        secureRegistry.excludePathPatterns("/punish/**");
//        secureRegistry.excludePathPatterns("/performance/**");
//        secureRegistry.excludePathPatterns("/information/**");
//        secureRegistry.excludePathPatterns("/member/**");
//        secureRegistry.excludePathPatterns("/shareholder/**");
//        secureRegistry.excludePathPatterns("/honor/**");
//        secureRegistry.excludePathPatterns("/dispatcher/**");
//        secureRegistry.excludePathPatterns("/employment/**");
//        secureRegistry.excludePathPatterns("/train/**");
//        secureRegistry.excludePathPatterns("/examination/**");
//        secureRegistry.excludePathPatterns("/experience/**");
//        secureRegistry.excludePathPatterns("/jurisdiction/**");
//        secureRegistry.excludePathPatterns("/permit/**");
//        secureRegistry.excludePathPatterns("/record/**");
//        secureRegistry.excludePathPatterns("/revoke/**");
//        secureRegistry.excludePathPatterns("/recordk/**");
//        secureRegistry.excludePathPatterns("/punish/**");
//        secureRegistry.excludePathPatterns("/blade-resource/attach/**");
//        secureRegistry.excludePathPatterns("/social/**");
//        secureRegistry.excludePathPatterns("/car/**");
//        secureRegistry.excludePathPatterns("/gun/**");
//        secureRegistry.excludePathPatterns("/equipage/**");
//        secureRegistry.excludePathPatterns("/blade-user/**");
//        secureRegistry.excludePathPatterns("/trainExam/**");
//        secureRegistry.excludePathPatterns("/trainingRegistration/**");
//        secureRegistry.excludePathPatterns("/workReport/**");
//        secureRegistry.excludePathPatterns("/seinspect/**");
//        secureRegistry.excludePathPatterns("/coinspect/**");
//        secureRegistry.excludePathPatterns("/dispatcherUnit/**");
//        secureRegistry.excludePathPatterns("/punish/**");
        return secureRegistry;
    }
src/main/java/org/springblade/common/config/CommonConfig.java
New file
@@ -0,0 +1,25 @@
package org.springblade.common.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
 * common 公共配置路径
 * @author zhongrj
 * @since 2022-01-06
 */
@ConfigurationProperties(prefix = "common")
@Component
public class CommonConfig {
    /**
     * sql connect
     */
    public static int socketPort;
    public void setSocketPort(int socketPort) {
        CommonConfig.socketPort = socketPort;
    }
}
src/main/java/org/springblade/common/config/ServerConfig.java
New file
@@ -0,0 +1,65 @@
package org.springblade.common.config;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
 * 服务器配置
 * @author zhongrj
 * @since 2022-01-07
 */
@Component
public class ServerConfig  implements ApplicationListener<WebServerInitializedEvent> {
    private int serverPort;
    /**
     * 获取服务器 url
     * @return
     */
    public String getUrl() {
        InetAddress address = null;
        try {
            address = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return "http://"+address.getHostAddress() +":"+this.serverPort;
    }
    /**
     * 获取服务器 ip
     * @return
     */
    public String getServerIp() {
        InetAddress address = null;
        try {
            address = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return address.getHostAddress();
    }
    /**
     * 获取服务器 hostname
     * @return
     */
    public String getServerHostName() {
        InetAddress address = null;
        try {
            address = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return address.getHostName();
    }
    @Override
    public void onApplicationEvent(WebServerInitializedEvent event) {
        this.serverPort = event.getWebServer().getPort();
    }
}
src/main/java/org/springblade/modules/FTP/monitor.java
@@ -36,7 +36,7 @@
            ftp.login(ftpUserName, ftpPassword);
            System.out.println("ftp.getReplyCode() = " + ftp.getReplyCode());
//            System.out.println("ftp.getReplyCode() = " + ftp.getReplyCode());
            // 检验登陆操作的返回码是否正确
            if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
src/main/java/org/springblade/modules/auth/endpoint/BladeTokenEndPoint.java
@@ -23,6 +23,7 @@
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.common.cache.CacheNames;
import org.springblade.common.config.ServerConfig;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.jwt.JwtUtil;
import org.springblade.core.jwt.props.JwtProperties;
@@ -40,11 +41,17 @@
import org.springblade.modules.auth.provider.TokenGranterBuilder;
import org.springblade.modules.auth.provider.TokenParameter;
import org.springblade.modules.auth.utils.TokenUtil;
import org.springblade.modules.loginrecord.entity.LoginRecord;
import org.springblade.modules.loginrecord.service.LoginRecordService;
import org.springblade.modules.system.entity.UserInfo;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Duration;
import java.util.Date;
import java.util.UUID;
import static org.springblade.core.cache.constant.CacheConstant.*;
@@ -64,6 +71,10 @@
    private final BladeRedis bladeRedis;
    private final JwtProperties jwtProperties;
    private final LoginRecordService loginRecordService;
    private final ServerConfig serverConfig;
    @ApiLog("登录用户验证")
    @PostMapping("/oauth/token")
@@ -93,9 +104,38 @@
            return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "未获得用户的角色信息");
        }
        //校验都通过,返回之前进行数据插入登录记录操作,刷新 token 不新增登录记录
        if (!grantType.equals("refresh_token")) {
            this.saveLoginRecord(userInfo);
        }
        //返回数据
        return TokenUtil.createAuthInfo(userInfo);
    }
    /**
     * 新增登录记录信息
     * @param userInfo
     */
    private void saveLoginRecord(UserInfo userInfo) {
        //创建对象
        LoginRecord loginRecord = new LoginRecord();
        //request 对象获取
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //数据封装
        loginRecord.setCreateTime(new Date());
        loginRecord.setServerIp(serverConfig.getServerIp());
        loginRecord.setServerHost(serverConfig.getServerHostName());
        loginRecord.setRequestUri(request.getRequestURI());
        loginRecord.setRemoteIp(request.getRemoteAddr());
        loginRecord.setDeptId(userInfo.getUser().getDeptId());
        loginRecord.setUserId(userInfo.getUser().getId());
        loginRecord.setType("2");
        loginRecord.setCreateBy(userInfo.getUser().getRealName());
        //新增
        loginRecordService.save(loginRecord);
    }
    @GetMapping("/oauth/logout")
    @ApiLog("用户登出")
src/main/java/org/springblade/modules/loginrecord/controller/LoginRecordController.java
New file
@@ -0,0 +1,122 @@
package org.springblade.modules.loginrecord.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.excel.util.ExcelUtil;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.modules.information.excel.ExportInformationExcel;
import org.springblade.modules.loginrecord.entity.LoginRecord;
import org.springblade.modules.loginrecord.excel.LoginRecordExcel;
import org.springblade.modules.loginrecord.service.LoginRecordService;
import org.springblade.modules.loginrecord.vo.LoginRecordVo;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * @author zhongrj
 * @time 2022-01-07
 * @desc 登录记录控制层
 */
@RestController
@AllArgsConstructor
@RequestMapping("/loginRecord")
public class LoginRecordController {
    private final LoginRecordService loginRecordService;
    /**
     * 自定义分页
     * @param query page,size
     * @param loginRecord 登录记录信息对象
     */
    @GetMapping("/page")
    public R<IPage<LoginRecordVo>> page(LoginRecordVo loginRecord, Query query) {
        IPage<LoginRecordVo> pages = loginRecordService.selectSecurityPaperPage(Condition.getPage(query), loginRecord);
        return R.data(pages);
    }
    /**
     * 新增
     * @param loginRecord 登录记录信息对象
     */
    @PostMapping("/save")
    @ApiOperation(value = "新增", notes = "传入loginRecord")
    public R save(@RequestBody LoginRecord loginRecord){
        return R.data(loginRecordService.save(loginRecord));
    }
    /**
     * 修改
     * @param loginRecord 登录记录信息对象
     */
    @PostMapping("/update")
    public R update(@RequestBody LoginRecord loginRecord){
        return R.status(loginRecordService.updateById(loginRecord));
    }
    /**
     * 新增或修改
     * @param loginRecord 登录记录信息对象
     */
    @PostMapping("/submit")
    public R submit(@RequestBody LoginRecord loginRecord){
        if (null==loginRecord.getId()){
            loginRecordService.save(loginRecord);
        }else {
            loginRecordService.updateById(loginRecord);
        }
        return R.data(loginRecord);
    }
    /**
     * 删除
     * @param ids 登录记录信息ids 数组
     */
    @PostMapping("/remove")
    public R remove(@ApiParam(value = "主键集合") @RequestParam String ids) {
        return R.status(loginRecordService.removeByIds(Func.toLongList(ids)));
    }
    /**
     * 详情
     * @param loginRecord 登录记录信息对象
     */
    @GetMapping("/detail")
    @ApiOperation(value = "详情", notes = "传入loginRecord")
    public R<LoginRecord> detail(LoginRecord loginRecord) {
        LoginRecord detail = loginRecordService.getOne(Condition.getQueryWrapper(loginRecord));
        return R.data(detail);
    }
    /**
     * 自定义分页(企业登录分页记录)
     * @param query page,size
     * @param loginRecord 登录记录信息对象
     */
    @GetMapping("/getInformationLoginPage")
    public R<IPage<LoginRecordVo>> getInformationLoginPage(LoginRecordVo loginRecord, Query query) {
        IPage<LoginRecordVo> pages = loginRecordService.getInformationLoginPage(Condition.getPage(query), loginRecord);
        return R.data(pages);
    }
    /**
     * 企业登录记录导出
     * @param response
     * @param loginRecord
     */
    @GetMapping("/export-login-record")
    public void exportLoginRecord(HttpServletResponse response,LoginRecordVo loginRecord){
        List<LoginRecordExcel> list = loginRecordService.exportLoginRecord(loginRecord);
        ExcelUtil.export(response, "企业登录记录数据" + DateUtil.time(), "企业登录记录数据表", list, LoginRecordExcel.class);
    }
}
src/main/java/org/springblade/modules/loginrecord/entity/LoginRecord.java
New file
@@ -0,0 +1,75 @@
package org.springblade.modules.loginrecord.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
 * 登录记录实体类
 * @author zhongrj
 * @time 2022-01-07
 */
@Data
@TableName("sys_login_record")
public class LoginRecord implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 登录记录主键id,非自增
     */
    @TableId(value = "id",type = IdType.ASSIGN_ID)
    private Long id;
    private Long userId;
    /**
     * 服务器名
     */
    private String serverHost;
    /**
     * 服务器ip
     */
    private String serverIp;
    /**
     * 登录类型  1:外网  2:内网
     */
    private String type;
    /**
     * 请求uri
     */
    private String requestUri;
    /**
     * 请求ip
     */
    private String remoteIp;
    /**
     * 请求人姓名
     */
    private String createBy;
    /**
     * 创建时间
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    /**
     * 公司部门id
     */
    private String deptId;
}
src/main/java/org/springblade/modules/loginrecord/excel/LoginRecordExcel.java
New file
@@ -0,0 +1,61 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.loginrecord.excel;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import java.io.Serializable;
/**
 * 登录记录导出
 * @author zhongrj
 * @since 2022-01-10
 */
@Data
@ColumnWidth(25)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class LoginRecordExcel implements Serializable {
    private static final long serialVersionUID = 1L;
    @ColumnWidth(30)
    @ExcelProperty("企业名称")
    private String deptName;
    @ColumnWidth(15)
    @ExcelProperty("企业类型")
    private String stats;
    @ColumnWidth(15)
    @ExcelProperty("所属辖区")
    private String jurisdictionName;
    @ColumnWidth(20)
    @ExcelProperty("最近一次登录时间")
    private String createTime;
    @ColumnWidth(15)
    @ExcelProperty("登录次数")
    private Integer num;
}
src/main/java/org/springblade/modules/loginrecord/mapper/LoginRecordMapper.java
New file
@@ -0,0 +1,41 @@
package org.springblade.modules.loginrecord.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
import org.springblade.modules.loginrecord.entity.LoginRecord;
import org.springblade.modules.loginrecord.excel.LoginRecordExcel;
import org.springblade.modules.loginrecord.vo.LoginRecordVo;
import java.util.List;
/**
 * 工商信息Mapper 接口
 * @author zhongrj
 */
public interface LoginRecordMapper extends BaseMapper<LoginRecord> {
    /**
     * 登录记录自定义分页
     * @param page
     * @param loginRecord
     * @return
     */
    List<LoginRecordVo> selectSecurityPaperPage(@Param("page") IPage<LoginRecordVo> page,@Param("loginRecord") LoginRecordVo loginRecord);
    /**
     * 自定义分页(企业登录分页记录)
     * @param page
     * @param loginRecord
     * @return
     */
    List<LoginRecordVo> getInformationLoginPage(@Param("page") IPage<LoginRecordVo> page,@Param("loginRecord") LoginRecordVo loginRecord);
    /**
     * 企业登录记录导出
     * @param loginRecord
     */
    List<LoginRecordExcel> exportLoginRecord(@Param("loginRecord") LoginRecordVo loginRecord);
}
src/main/java/org/springblade/modules/loginrecord/mapper/LoginRecordMapper.xml
New file
@@ -0,0 +1,141 @@
<?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="org.springblade.modules.loginrecord.mapper.LoginRecordMapper">
    <!--登录记录自定义分页信息-->
    <select id="selectSecurityPaperPage" resultType="org.springblade.modules.loginrecord.vo.LoginRecordVo">
        select
            slr.*,
            bd.dept_name deptName
        from
            sys_login_record slr
        left join blade_dept bd on bd.id = slr.dept_id
        <if test="loginRecord.deptName!=null and loginRecord.deptName!=''">
            bd.dept_name like concat('%',#{loginRecord.deptName},'%')
        </if>
        <if test="loginRecord.createBy!=null and loginRecord.createBy!=''">
            slr.create_by like concat('%',#{loginRecord.createBy},'%')
        </if>
        order by slr.id desc
    </select>
    <!--自定义分页(企业登录分页记录)-->
    <select id="getInformationLoginPage" resultType="org.springblade.modules.loginrecord.vo.LoginRecordVo">
        select * from (
        SELECT
        a.enterpriseName deptName,
        a.stats,
        sj.dept_name jurisdictionName,
        ifnull( b.c, 0 ) num,
        c.create_time createTime
        FROM
        (
        SELECT
        si.*
        FROM
        sys_information si
        LEFT JOIN blade_dept bd ON si.departmentid = bd.id
        WHERE
        bd.is_deleted = 0
        AND bd.dept_category = 1
        ) a
        left join
        (
        SELECT count( * ) c, dept_id FROM `sys_login_record`
        where 1=1
        and type = 1
        <if test="loginRecord.startTime!=null and loginRecord.startTime!=''">
            and date(create_time) &gt;= #{loginRecord.startTime}
        </if>
        <if test="loginRecord.endTime!=null and loginRecord.endTime!=''">
            and date(create_time) &lt;= #{loginRecord.endTime}
        </if>
        GROUP BY dept_id
        ) b ON a.departmentid = b.dept_id
        left join
        (
        select dept_id,max(create_time) create_time from sys_login_record GROUP BY dept_id
        ) c on a.departmentid = c.dept_id
        left join sys_jurisdiction sj on a.jurisdiction = sj.id
        where 1=1
        <if test="loginRecord.jurisdiction!=null and loginRecord.jurisdiction!='' and loginRecord.jurisdiction!='1372091709474910209'">
            and (sj.id = #{loginRecord.jurisdiction} or sj.parent_id = #{loginRecord.jurisdiction})
        </if>
        <if test="loginRecord.deptName!=null and loginRecord.deptName!=''">
            and a.enterpriseName like concat('%',#{loginRecord.deptName},'%')
        </if>
        <if test="loginRecord.stats!=null and loginRecord.stats!=''">
            and a.stats = #{loginRecord.stats}
        </if>
        ) r where 1=1
        <if test="loginRecord.types ==1">
            and r.num = 0
        </if>
        <if test="loginRecord.types ==2">
            and r.num > 0
        </if>
    </select>
    <!--企业登录记录导出-->
    <select id="exportLoginRecord" resultType="org.springblade.modules.loginrecord.excel.LoginRecordExcel">
        select * from (
        SELECT
        a.enterpriseName deptName,
        case when a.stats=0 then '自招保安单位'
        when a.stats=2 then '本市保安公司'
        when a.stats=4 then '分公司'
        when a.stats=1 then '保安培训学校'
        else "其他" end as stats,
        sj.dept_name jurisdictionName,
        ifnull( b.c, 0 ) num,
        c.create_time createTime
        FROM
        (
        SELECT
        si.*
        FROM
        sys_information si
        LEFT JOIN blade_dept bd ON si.departmentid = bd.id
        WHERE
        bd.is_deleted = 0
        AND bd.dept_category = 1
        ) a
        left join
        (
        SELECT count( * ) c, dept_id FROM `sys_login_record`
        where 1=1
        and type = 1
        <if test="loginRecord.startTime!=null and loginRecord.startTime!='' and loginRecord.startTime!='undefined'">
            and date(create_time) &gt;= #{loginRecord.startTime}
        </if>
        <if test="loginRecord.endTime!=null and loginRecord.endTime!='' and loginRecord.endTime!='undefined'">
            and date(create_time) &lt;= #{loginRecord.endTime}
        </if>
        GROUP BY dept_id
        ) b ON a.departmentid = b.dept_id
        left join
        (
        select dept_id,max(create_time) create_time from sys_login_record GROUP BY dept_id
        ) c on a.departmentid = c.dept_id
        left join sys_jurisdiction sj on a.jurisdiction = sj.id
        where 1=1
        <if test="loginRecord.jurisdiction!=null and loginRecord.jurisdiction!='' and loginRecord.jurisdiction!='1372091709474910209'">
            and (sj.id = #{loginRecord.jurisdiction} or sj.parent_id = #{loginRecord.jurisdiction})
        </if>
        <if test="loginRecord.deptName!=null and loginRecord.deptName!=''">
            and a.enterpriseName like concat('%',#{loginRecord.deptName},'%')
        </if>
        <if test="loginRecord.stats!=null and loginRecord.stats!=''">
            and a.stats = #{loginRecord.stats}
        </if>
        ) r where 1=1
        <if test="loginRecord.types ==1">
            and r.num = 0
        </if>
        <if test="loginRecord.types ==2">
            and r.num > 0
        </if>
    </select>
</mapper>
src/main/java/org/springblade/modules/loginrecord/service/LoginRecordService.java
New file
@@ -0,0 +1,37 @@
package org.springblade.modules.loginrecord.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.modules.loginrecord.entity.LoginRecord;
import org.springblade.modules.loginrecord.excel.LoginRecordExcel;
import org.springblade.modules.loginrecord.vo.LoginRecordVo;
import java.util.List;
/**
 * 登录记录服务类
 * @author zhongrj
 */
public interface LoginRecordService extends IService<LoginRecord> {
    /**
     * 自定义分页
     * @param page
     * @param loginRecord 登录记录信息对象
     */
    IPage<LoginRecordVo> selectSecurityPaperPage(IPage<LoginRecordVo> page, LoginRecordVo loginRecord);
    /**
     * 自定义分页(企业登录分页记录)
     * @param page
     * @param loginRecord
     * @return
     */
    IPage<LoginRecordVo> getInformationLoginPage(IPage<LoginRecordVo> page, LoginRecordVo loginRecord);
    /**
     * 企业登录记录导出
     * @param loginRecord
     */
    List<LoginRecordExcel> exportLoginRecord(LoginRecordVo loginRecord);
}
src/main/java/org/springblade/modules/loginrecord/service/impl/LoginRecordServiceImpl.java
New file
@@ -0,0 +1,55 @@
package org.springblade.modules.loginrecord.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import org.springblade.modules.loginrecord.entity.LoginRecord;
import org.springblade.modules.loginrecord.excel.LoginRecordExcel;
import org.springblade.modules.loginrecord.mapper.LoginRecordMapper;
import org.springblade.modules.loginrecord.service.LoginRecordService;
import org.springblade.modules.loginrecord.vo.LoginRecordVo;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * 保安员证管理服务实现类
 * @author zhongrj
 * @since 2022-01-07
 */
@Service
@AllArgsConstructor
public class LoginRecordServiceImpl extends ServiceImpl<LoginRecordMapper, LoginRecord> implements LoginRecordService {
    /**
     * 自定义分页
     * @param page
     * @param loginRecord 登录记录信息对象
     * @return
     */
    @Override
    public IPage<LoginRecordVo> selectSecurityPaperPage(IPage<LoginRecordVo> page, LoginRecordVo loginRecord) {
        return page.setRecords(baseMapper.selectSecurityPaperPage(page,loginRecord));
    }
    /**
     * 自定义分页(企业登录分页记录)
     * @param page
     * @param loginRecord
     * @return
     */
    @Override
    public IPage<LoginRecordVo> getInformationLoginPage(IPage<LoginRecordVo> page, LoginRecordVo loginRecord) {
        return page.setRecords(baseMapper.getInformationLoginPage(page,loginRecord));
    }
    /**
     * 企业登录记录导出
     * @param loginRecord
     */
    @Override
    public List<LoginRecordExcel> exportLoginRecord(LoginRecordVo loginRecord) {
        return baseMapper.exportLoginRecord(loginRecord);
    }
}
src/main/java/org/springblade/modules/loginrecord/vo/LoginRecordVo.java
New file
@@ -0,0 +1,57 @@
package org.springblade.modules.loginrecord.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springblade.modules.loginrecord.entity.LoginRecord;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
 * 登录记录vo
 * @author zhongrj
 * @since 2022-01-07
 */
@Data
public class LoginRecordVo extends LoginRecord implements Serializable {
    /**
     * 公司名称
     */
    private String deptName;
    /**
     * 辖区
     */
    private String jurisdiction;
    /**
     * 开始时间
     */
    private String startTime;
    /**
     * 截止时间
     */
    private String endTime;
    /**
     * 企业属性
     */
    private String stats;
    /**
     * 辖区名称
     */
    private String jurisdictionName;
    /**
     * 数量
     */
    private Integer num;
    /**
     * 是否无登录记录 1:无登录记录  2:有登录记录
     */
    private Integer types;
}
src/main/java/org/springblade/modules/nettyServer/ChannelMap.java
New file
@@ -0,0 +1,36 @@
package org.springblade.modules.nettyServer;
import io.netty.channel.Channel;
import java.util.concurrent.ConcurrentHashMap;
public class ChannelMap {
    public static int channelNum=0;
    private static ConcurrentHashMap<String, Channel> channelHashMap=null;//concurrentHashmap以解决多线程冲突
    public static ConcurrentHashMap<String, Channel> getChannelHashMap() {
        return channelHashMap;
    }
    public static Channel getChannelByName(String name){
        if(channelHashMap==null||channelHashMap.isEmpty()){
            return null;
        }
        return channelHashMap.get(name);
    }
    public static void addChannel(String name, Channel channel){
        if(channelHashMap==null){
            channelHashMap=new ConcurrentHashMap<String, Channel>(10);
        }
        channelHashMap.put(name,channel);
        channelNum++;
    }
    public static int removeChannelByName(String name){
        if(channelHashMap.containsKey(name)){
            channelHashMap.remove(name);
            return 0;
        }else{
            return 1;
        }
    }
}
src/main/java/org/springblade/modules/nettyServer/NettyConfig.java
New file
@@ -0,0 +1,45 @@
package org.springblade.modules.nettyServer;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.util.concurrent.ConcurrentHashMap;
public class NettyConfig {
    /**
     * 存储每一个客户端接入进来时的channel对象
     */
    public static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    /**
     * 定义一个channel组,管理所有channel
     * GlobalEventExecutor.INSTANCE 是全局的事件执行器,是一个单例
     */
    private static ChannelGroup channelGroup = new DefaultChannelGroup("用户管理组", GlobalEventExecutor.INSTANCE);
    /**
     * 存放用户与chanel 的对应的信息,用于给指定用户发送信息
     */
    private static ConcurrentHashMap<String, Channel> userChannelMap = new ConcurrentHashMap<>();
    public NettyConfig() {
    }
    /**
     * 获取用户channel 组
     * @return
     */
    public static ChannelGroup getChannelGroup() {
        return channelGroup;
    }
    /**
     * 获取用户channel map
     * @return
     */
    public static ConcurrentHashMap<String, Channel> getUserChannelMap() {
        return userChannelMap;
    }
}
src/main/java/org/springblade/modules/nettyServer/Server.java
New file
@@ -0,0 +1,86 @@
package org.springblade.modules.nettyServer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
    private int port;
    private ServerSocketChannel serverSocketChannel;
    public Server(int port){
        this.port = port;
        bind();
    }
    private void bind() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //服务端要建立两个group,一个负责接收客户端的连接,一个负责处理数据传输
                //连接处理group
                EventLoopGroup boss = new NioEventLoopGroup();
                //事件处理group
                EventLoopGroup worker = new NioEventLoopGroup();
                ServerBootstrap bootstrap = new ServerBootstrap();
                // 绑定处理group
                bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
                    //保持连接数
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    //有数据立即发送
                    .option(ChannelOption.TCP_NODELAY, true)
                    //保持连接
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    //处理新连接
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel sc) throws Exception {
                            // 增加任务处理
                            ChannelPipeline p = sc.pipeline();
                            p.addLast(
//                                        //使用了netty自带的编码器和解码器
//                                        new StringDecoder(),
//                                        new StringEncoder(),
                                //心跳检测,读超时,写超时,读写超时
                                //new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS),
                                //自定义的处理器
                                new ServerHandler());
                        }
                    });
                //绑定端口,同步等待成功
                ChannelFuture future;
                try {
                    future = bootstrap.bind(port).sync();
                    if (future.isSuccess()) {
                        serverSocketChannel = (ServerSocketChannel) future.channel();
                        System.out.println("服务端启动成功,端口:"+port);
                    } else {
                        System.out.println("服务端启动失败!");
                    }
                    //等待服务监听端口关闭,就是由于这里会将线程阻塞,导致无法发送信息,所以我这里开了线程
                    future.channel().closeFuture().sync();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                finally {
                    //优雅地退出,释放线程池资源
                    boss.shutdownGracefully();
                    worker.shutdownGracefully();
                }
            }
        });
        thread.start();
    }
    public void sendMessage(Object msg){
        if(serverSocketChannel != null){
            serverSocketChannel.writeAndFlush(msg);
        }
    }
}
src/main/java/org/springblade/modules/nettyServer/ServerHandler.java
New file
@@ -0,0 +1,89 @@
package org.springblade.modules.nettyServer;
import com.alibaba.fastjson.JSONObject;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import org.springblade.modules.webscoket.service.IPushMsgService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class ServerHandler extends ChannelInboundHandlerAdapter {
    private String reg_LA = "LA[d]{8}[d|A-F]{12}[d|A-F]{8}[d|A-Z]{2}[d|A-F]{4}[x2A][d|A-F]{6}[#@]";
    private String reg_LB = "LB[\\d|A-F]{12}[\\x2A].*[#@]";
    private String reg_LB2 = "LB[\\d|A-F]{6}[\\x2A].*[#@]";
    private String reg_LD = "LD[d]{8}[d|A-F]{12}:[A-Z]{4}[\\x2A].*[#@]";
    private ConcurrentHashMap<String, Channel> sessionChannelMap = new ConcurrentHashMap<String, Channel>();
    private static ServerHandler serverHandler;
    @PostConstruct
    public void init() {
        serverHandler = this;
    }
    /**
     * 客户端与服务端创建连接的时候调用
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("CTX:" + ctx.channel());
        System.out.println("客户端与服务端连接开始...");
    }
    /**
     * 客户端与服务端断开连接时调用
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端与服务端连接关闭...");
        NettyConfig.group.remove(ctx.channel());
    }
    /**
     * 服务端接收客户端发送过来的数据结束之后调用
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
        System.out.println("信息接收完毕...");
    }
    /**
     * 工程出现异常的时候调用
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
    /**
     * 服务端处理客户端websocket请求的核心方法,这里接收了客户端发来的信息
     */
    @Override
    public void channelRead(ChannelHandlerContext channelHandlerContext, Object info) throws Exception {
        System.out.println("接收到了:" + info);
        ByteBuf buf = (ByteBuf) info;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        String content = body;
        System.out.println("接收客户端数据:" + body);
    }
}
src/main/java/org/springblade/modules/runner/MyRunner.java
New file
@@ -0,0 +1,24 @@
package org.springblade.modules.runner;
import okhttp3.WebSocket;
import org.springblade.common.config.CommonConfig;
import org.springblade.common.config.FtpConfig;
import org.springblade.modules.webscoket.WebSocketServer;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
 * 自定义启动(项目启动即启动)
 * @author zhongrj
 * @since 2022-01-06
 */
@Component
public class MyRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("websocketServer 开始启动!");
        //启动即创建webSocketServer
        WebSocketServer socketServer = new WebSocketServer(CommonConfig.socketPort);
    }
}
src/main/java/org/springblade/modules/system/controller/UserController.java
@@ -56,6 +56,7 @@
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -193,6 +194,15 @@
    }
    /**
     * 修改用户信息
     */
    @PostMapping("/updateUser")
    public R updateUser(@Valid @RequestBody User user) {
        user.setUpdateTime(new Date());
        return R.status(userService.updateById(user));
    }
    /**
     * 删除
     */
    @PostMapping("/remove")
src/main/java/org/springblade/modules/webscoket/ChannelSupervise.java
New file
@@ -0,0 +1,38 @@
package org.springblade.modules.webscoket;
import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class ChannelSupervise {
    private   static ChannelGroup GlobalGroup=new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    private  static ConcurrentMap<String, ChannelId> ChannelMap=new ConcurrentHashMap();
    private  static Map<String, String> map = new HashMap<String, String>();;
    public  static void addChannel(Channel channel, String name){
        GlobalGroup.add(channel);
        ChannelMap.put(channel.id().asShortText(),channel.id());
        map.put(channel.id().asShortText(),name);
    }
    public static void removeChannel(Channel channel){
        GlobalGroup.remove(channel);
        ChannelMap.remove(channel.id().asShortText());
        map.remove(channel.id().asShortText());
    }
    public static Channel findChannel(String id){
        return GlobalGroup.find(ChannelMap.get(id));
    }
    public static String findName(String id){
    return map.get(id);
    }
    public static void send2All(TextWebSocketFrame tws){
        GlobalGroup.writeAndFlush(tws);
    }
}
src/main/java/org/springblade/modules/webscoket/WebSocketHandler.java
New file
@@ -0,0 +1,193 @@
package org.springblade.modules.webscoket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.AttributeKey;
import io.netty.util.CharsetUtil;
import org.springblade.modules.nettyServer.NettyConfig;
import org.springblade.modules.system.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class WebSocketHandler extends SimpleChannelInboundHandler<Object> {
    private WebSocketServerHandshaker handshaker;
    private  String on=null;
    @Autowired
    private IUserService userService;
    private static WebSocketHandler webSocketHandler;
    @PostConstruct
    public void init() {
        webSocketHandler = this;
    }
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            //以http请求形式接入,但是走的是websocket
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            //处理websocket客户端的消息
            handlerWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }
    /**
     * 客户端加入连接
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //添加连接
        System.out.println("客户端加入连接:" + ctx.channel());
        //ChannelSupervise.addChannel(ctx.channel());
    }
    /**
     * 客户端离开连接
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //断开连接
        System.out.println("客户端断开连接:" + ctx.channel());
        //用户离线状态
        String name = ChannelSupervise.findName(ctx.channel().id().asShortText());
        if ( name != null &&!name.equals("ping") ){
            String num="0";
            //工作状态(0闲置,1工作中)
            String workSt = "0";
//            webSocketHandler.userService.updateUser(num,name,workSt);
            //ChannelSupervise.removeChannel(ctx.channel());
            NettyConfig.getChannelGroup().remove(ctx.channel());
            removeUserId(ctx);
        }
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
    private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws NumberFormatException, Exception {
        System.out.println("ctx = " + ctx);
        System.out.println("frame = " + frame);
        // 判断是否关闭链路的指令
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        // 判断是否ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(
                new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 本例程仅支持文本消息,不支持二进制消息
        if (!(frame instanceof TextWebSocketFrame)) {
            System.out.println("本例程仅支持文本消息,不支持二进制消息");
            throw new UnsupportedOperationException(String.format(
                "%s frame types not supported", frame.getClass().getName()));
        }
        // 返回应答消息
        String request = ((TextWebSocketFrame) frame).text();
        if (!request.equals("ping")){
            NettyConfig.getUserChannelMap().put(request,ctx.channel());
            //将用户id作为自定义属性加入到channel 中,方便随时channel中获取用户id
            AttributeKey<String> key = AttributeKey.valueOf("userId");
            ctx.channel().attr(key).setIfAbsent(request);
            //把用户信息添加到通道里
            ChannelSupervise.addChannel(ctx.channel(),request);
            //用户在线状态
            this.on=request;
            //在线状态(0掉线,1在线)
            String num="1";
            //工作状态(0闲置,1工作中)
            String workSt = "0";
//            webSocketHandler.userService.updateUser(num,request,workSt);
        }
    }
    /**
     * 唯一的一次http请求,用于创建websocket
     *
     * @throws InterruptedException
     */
    private void handleHttpRequest(final ChannelHandlerContext ctx,
                                   FullHttpRequest req) throws InterruptedException {
        //要求Upgrade为websocket,过滤掉get/Post
        if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) {
            //若不是websocket方式,则创建BAD_REQUEST的req,返回给客户端
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        //握手
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
            "ws://localhost:9034/websocket", null, false);
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory
                .sendUnsupportedVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), req);
        }
    }
    /**
     * 拒绝不合法的请求,并返回错误信息
     */
    private static void sendHttpResponse(ChannelHandlerContext ctx,
                                         FullHttpRequest req, DefaultFullHttpResponse res) {
        // 返回应答给客户端
        if (res.status().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(),
                CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        // 如果是非Keep-Alive,关闭连接
//        if (!isKeepAlive(req) || res.status().code() != 200) {
//            f.addListener(ChannelFutureListener.CLOSE);
//        }
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
    /**
     * 删除用户与channel 对应关系
     * @param ctx
     */
    private void removeUserId(ChannelHandlerContext ctx){
        AttributeKey<String> key = AttributeKey.valueOf("userId");
        String userId = ctx.channel().attr(key).get();
        NettyConfig.getUserChannelMap().remove(userId);
    }
}
src/main/java/org/springblade/modules/webscoket/WebSocketServer.java
New file
@@ -0,0 +1,59 @@
package org.springblade.modules.webscoket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
public class WebSocketServer {
    private int port = 9034;
    public WebSocketServer(int port) {
        bind(port);
    }
    public void bind(int port) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                EventLoopGroup bossGroup = new NioEventLoopGroup();
                EventLoopGroup workerGroup = new NioEventLoopGroup();
                try {
                    ServerBootstrap serverBootstrap = new ServerBootstrap();
                    serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                        .handler(new LoggingHandler(LogLevel.INFO))
                        //保持连接
                        .childOption(ChannelOption.SO_KEEPALIVE, true)
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) {
//                                ch.pipeline().addLast("logging",new LoggingHandler("DEBUG"));//设置log监听器,并且日志级别为debug,方便观察运行流程
                                ch.pipeline().addLast("http-codec", new HttpServerCodec());//设置解码器
                                ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));//聚合器,使用websocket会用到
                                ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());//用于大数据的分区传输
                                ch.pipeline().addLast("handler", new WebSocketHandler());//自定义的业务handler
                            }
                        });
                    ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
                    System.out.println("WebSocketServer启动成功");
                    channelFuture.channel().closeFuture().sync();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    bossGroup.shutdownGracefully();
                    workerGroup.shutdownGracefully();
                }
            }
        });
        thread.start();
    }
}
src/main/java/org/springblade/modules/webscoket/controller/PushMsgController.java
New file
@@ -0,0 +1,30 @@
package org.springblade.modules.webscoket.controller;
import org.springblade.modules.webscoket.service.IPushMsgService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author lq
 * @date 2020/4/1 11:22
 */
@RestController
public class PushMsgController {
    @Autowired
    private IPushMsgService pushMsgService;
    @PostMapping("/pushUser")
    public String pushUser(String userId,String msg){
        pushMsgService.pushMsg(userId, msg);
        return "消息发送成功:"+msg;
    }
    @PostMapping("/pushAll")
    public String pushAll(String msg){
        pushMsgService.pushMsg(msg);
        return "消息发送成功:"+msg;
    }
}
src/main/java/org/springblade/modules/webscoket/index.html
New file
@@ -0,0 +1,57 @@
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset = utf-8"/>
    <title>WebSocket客户端</title>
    <script type="text/javascript">
        var socket;
        if(!window.WebSocket){
            window.WebSocket = window.MozWebSocket;
        }
        if(window.WebSocket){
            socket = new WebSocket("ws://localhost:9034/websocket");
            socket.onmessage = function(event){
                var ta = document.getElementById('responseContent');
                ta.value += event.data + "\r\n";
            };
            socket.onopen = function(event){
                var ta = document.getElementById('responseContent');
                ta.value = "你当前的浏览器支持WebSocket,请进行后续操作\r\n";
            };
            socket.onclose = function(event){
                var ta = document.getElementById('responseContent');
                ta.value = "";
                ta.value = "WebSocket连接已经关闭\r\n";
            };
        }else{
            alert("您的浏览器不支持WebSocket");
        }
        function send(message){
            if(!window.WebSocket){
                return;
            }
            if(socket.readyState == WebSocket.OPEN){
                socket.send(message);
            }else{
                alert("WebSocket连接没有建立成功!!");
            }
        }
    </script>
</head>
<body>
<form onSubmit="return false;">
    <input type = "text" name = "message" value = ""/>
    <br/><br/>
    <input type = "button" value = "发送WebSocket请求消息" onClick = "send(this.form.message.value)"/>
    <hr color="red"/>
    <h2>客户端接收到服务端返回的应答消息</h2>
    <textarea id = "responseContent" style = "width:1024px; height:300px"></textarea>
</form>
</body>
</html>
src/main/java/org/springblade/modules/webscoket/service/IPushMsgService.java
New file
@@ -0,0 +1,21 @@
package org.springblade.modules.webscoket.service;
/**
 * @author 123456
 */
public interface IPushMsgService {
    /**
     * 给指定用户发送消息
     * @param userId
     * @param msg
     */
    void pushMsg(String userId,String msg);
    /**
     * 给所有用户发送消息
     * @param msg
     */
    void pushMsg(String msg);
}
src/main/java/org/springblade/modules/webscoket/service/impl/PushMsgServiceImpl.java
New file
@@ -0,0 +1,32 @@
package org.springblade.modules.webscoket.service.impl;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.springblade.modules.nettyServer.NettyConfig;
import org.springblade.modules.webscoket.service.IPushMsgService;
import org.springframework.stereotype.Service;
/**
 * @author lq
 * @date 2020/4/1 11:20
 */
@Service
public class PushMsgServiceImpl implements IPushMsgService {
    @Override
    public void pushMsg(String userId, String msg) {
        Channel channel = NettyConfig.getUserChannelMap().get(userId);
        if (channel != null){
            channel.writeAndFlush(new TextWebSocketFrame(msg));
        }
    }
    @Override
    public void pushMsg(String msg) {
        ChannelGroup group = NettyConfig.getChannelGroup();
        String name = group.name();
        System.out.println("空间大小:"+group.size()+",名字:"+name);
        group.writeAndFlush(new TextWebSocketFrame(msg));
    }
}
src/main/resources/application-dev.yml
@@ -36,6 +36,11 @@
  minioPath: /usr/local/minio/data/zhba/upload/picture
  jsonUrl: /home/song/anbao/
#公共配置
common:
  socket_port: 9034
    # PostgreSQL
    #url: jdbc:postgresql://127.0.0.1:5432/bladex_boot
    #username: postgres
src/main/resources/application-test.yml
@@ -32,6 +32,10 @@
  minioPath: D:\ftptp\
  jsonUrl: D:\anbao\
#公共配置
common:
  socketPort: 9034
    # PostgreSQL
    #url: jdbc:postgresql://127.0.0.1:5432/bladex_boot
    #username: postgres
src/main/resources/application.yml
@@ -161,7 +161,9 @@
  #token配置
  token:
    #是否有状态
    state: false
    state: true
    #只可同时在线一人
    single: true
  #redis序列化方式
  redis:
    serializer-type: protostuff
@@ -194,15 +196,15 @@
    #接口放行
    skip-url:
      - /blade-test/**
      - /liveLocation/**
      - /locus/**
      - /examScore/**
      - /trainingRegistration/**
      - /directive/**
      - /blade-user/**
      - /examPayment/**
      - /apply/**
      - /investigate/**
#      - /liveLocation/**
#      - /locus/**
#      - /examScore/**
#      - /trainingRegistration/**
#      - /directive/**
#      - /blade-user/**
#      - /examPayment/**
#      - /apply/**
#      - /investigate/**
    #授权认证配置
    auth:
      - method: ALL