1. 登录逻辑修改,新增登录记录
2. 登录记录查询导出新增
9 files modified
22 files added
| | |
| | | <version>2.4</version> |
| | | <classifier>jdk15</classifier> |
| | | </dependency> |
| | | <!--netty--> |
| | | <dependency> |
| | | <groupId>io.netty</groupId> |
| | | <artifactId>netty-all</artifactId> |
| | | </dependency> |
| | | </dependencies> |
| | | |
| | | <build> |
| | |
| | | import org.springblade.core.launch.BladeApplication; |
| | | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| | | import org.springframework.scheduling.annotation.EnableScheduling; |
| | | |
| | | /** |
| | | * 启动器 |
| | | * |
| | |
| | | */ |
| | | @EnableScheduling |
| | | @SpringBootApplication |
| | | public class Application { |
| | | public class Application{ |
| | | |
| | | public static void main(String[] args) { |
| | | BladeApplication.run(CommonConstant.APPLICATION_NAME, Application.class, args); |
| | | } |
| | | |
| | | } |
| | | |
| | |
| | | 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; |
| | | } |
| | | |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| New file |
| | |
| | | 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(); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | ftp.login(ftpUserName, ftpPassword); |
| | | |
| | | System.out.println("ftp.getReplyCode() = " + ftp.getReplyCode()); |
| | | // System.out.println("ftp.getReplyCode() = " + ftp.getReplyCode()); |
| | | |
| | | // 检验登陆操作的返回码是否正确 |
| | | if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) { |
| | |
| | | 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; |
| | |
| | | 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.*; |
| | |
| | | |
| | | private final BladeRedis bladeRedis; |
| | | private final JwtProperties jwtProperties; |
| | | |
| | | private final LoginRecordService loginRecordService; |
| | | |
| | | private final ServerConfig serverConfig; |
| | | |
| | | @ApiLog("登录用户验证") |
| | | @PostMapping("/oauth/token") |
| | |
| | | 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("用户登出") |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| New file |
| | |
| | | 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; |
| | | |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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; |
| | | |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| New file |
| | |
| | | <?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) >= #{loginRecord.startTime} |
| | | </if> |
| | | <if test="loginRecord.endTime!=null and loginRecord.endTime!=''"> |
| | | and date(create_time) <= #{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) >= #{loginRecord.startTime} |
| | | </if> |
| | | <if test="loginRecord.endTime!=null and loginRecord.endTime!='' and loginRecord.endTime!='undefined'"> |
| | | and date(create_time) <= #{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> |
| New file |
| | |
| | | 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); |
| | | } |
| New file |
| | |
| | | |
| | | 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); |
| | | } |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | 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); |
| | | |
| | | } |
| | | |
| | | |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | 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; |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 修改用户信息 |
| | | */ |
| | | @PostMapping("/updateUser") |
| | | public R updateUser(@Valid @RequestBody User user) { |
| | | user.setUpdateTime(new Date()); |
| | | return R.status(userService.updateById(user)); |
| | | } |
| | | |
| | | /** |
| | | * 删除 |
| | | */ |
| | | @PostMapping("/remove") |
| New file |
| | |
| | | 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); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | 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(); |
| | | } |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | 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); |
| | | |
| | | } |
| New file |
| | |
| | | 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)); |
| | | } |
| | | } |
| | |
| | | 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 |
| | |
| | | minioPath: D:\ftptp\ |
| | | jsonUrl: D:\anbao\ |
| | | |
| | | #公共配置 |
| | | common: |
| | | socketPort: 9034 |
| | | |
| | | # PostgreSQL |
| | | #url: jdbc:postgresql://127.0.0.1:5432/bladex_boot |
| | | #username: postgres |
| | |
| | | #token配置 |
| | | token: |
| | | #是否有状态 |
| | | state: false |
| | | state: true |
| | | #只可同时在线一人 |
| | | single: true |
| | | #redis序列化方式 |
| | | redis: |
| | | serializer-type: protostuff |
| | |
| | | #接口放行 |
| | | 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 |