智能巡检系统后台部分是采用目前非常流行的SpringBoot框架实现,其中数据库访问采用mybatis-plus相关技术实现。使用高性能的Redis缓存数据库,作为应用程序的缓存层,来存储和管理数据。智能巡检系统使用国内java最流行的技术栈,提供各类经过各种项目验证的组件方便在业务开发时的调用,例如druid、HuTool、easyPOI、fastJson、jsoup、freemarker等等。智能巡检系统的核心业务功能是班组管理、路线管理、日常巡检和上报记录,感兴趣或者有相关技术能力的公司或个人可以基于此项目开发出适合自己的衍生项目。
本文档的作用只是起到抛砖引玉的作用,供广大爱好者或者相关行业工作者学习或借鉴。
rcs └── src #源码目录 | └── main | | ├── java | | | └── com | | | └── iqiqiqi #公司名 | | | └── rcs #项目名 | | | ├── common #通用模块部分 | | | | ├── annotation #自定义注解 | | | | ├── aspect #切面 | | | | ├── config #项目配置 | | | | ├── constant #常量 | | | | ├── convert #日期转换 | | | | ├── dao #BaseDao | | | | ├── entity #BaseEntity | | | | ├── exception #统一异常处理 | | | | ├── interceptor #自定义拦截器 | | | | ├── page #通用分页处理 | | | | ├── redis #Redis相关封装 | | | | ├── service #BaseService | | | | ├── utils #各种小组件,例如文件处理,字典处理等等 | | | | ├── validator #后端校验 | | | | └── xss #防xss攻击 | | | ├── modules #功能模块 | | | | ├── log #日志管理 | | | | ├── mobile #手机端后台接口 | | | | ├── oss #对象存储服务 | | | | ├── pointmgt #巡检点管理 | | | | ├── recordmgt #巡检记录管理 | | | | ├── routingmgt #巡检路线管理 | | | | ├── security #安全相关 | | | | ├── sys #系统管理 | | | | └── teamsmgt #班组管理 | | | ├── PlatformApplication.java #SpringBoot入口类 | | └── resources | | ├── application-dev.yml #开发环境 | | ├── application-prod.yml #生产环境 | | ├── application-test.yml #测试环境 | | ├── application.yml #公共配置 | | ├── banner.txt #启动时显示的文字 | | ├── dev | | | └── application.yml #maven打包时用的文件 | | ├── excel | | | └── pointmgt | | | | ├── RoutineCheckPointExportTemplate.xml #excel导出模版 | | | └── RoutineCheckPointImportTemplate.xml #excel导入模版 | | | └── teamsmgt | | | ├── RoutineCheckTeamsExportTemplate.xml #excel导出模版 | | | └── RoutineCheckTeamsImportTemplate.xml #excel导入模版 | | ├── i18n #多语言 | | | ├── messages.properties | | | ├── messages_en_US.properties | | | ├── messages_zh_CN.properties | | ├── logback-spring.xml #日志配置 | | ├── mapper #mybatisplus mapper文件 | | | ├── log | | | ├── oss | | | ├── pointmgt | | | ├── recordmgt | | | ├── routingmgt | | | ├── sys | | | ├── teamsmgt | | ├── prod | | | └── application.yml #maven打包时用的文件
一个成熟的后台项目包含很多技术框架类的内容,例如组件库,安全处理,统一日志处理,多语言,统一异常处理,excel导入导出等等。由于此智能巡检系统rcs是一个完整的项目,虽然业务功能点不多,但技术框架该有的基础功能已经基本涵盖(工作流相关没有包括进来),以下从两个功能点代码进行讲解,起到抛砖引玉的作用,完整的代码学习还需要自行在项目中或者自行debug进行学习。
对应《rcs代码讲解(手机端部分)》,此处解析java端对应的后台部分。
@RestController
@Api(tags = "手机端登录管理")
@RequestMapping("mobile")
public class MobileLoginController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysUserTokenService sysUserTokenService;
@Autowired
private SysLogLoginService sysLogLoginService;
@Autowired
private CaptchaService captchaService;
@Autowired
private UserDetailRedis userDetailRedis;
@Autowired
private JwtUtils jwtUtils;
@Autowired
private JwtProperties jwtProperties;
@PostMapping("login")
@ApiOperation(value = "登录")
public Result login(HttpServletRequest request, @RequestBody LoginDTO login) {
//效验数据
ValidatorUtils.validateEntity(login);
//用户信息
SysUserDTO user = sysUserService.getByUsername(login.getUsername());
SysLogLoginEntity log = new SysLogLoginEntity();
log.setOperation(LoginOperationEnum.LOGIN.value());
log.setCreateDate(new Date());
log.setIp(IpUtils.getIpAddr(request));
log.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
log.setIp(IpUtils.getIpAddr(request));
//用户不存在
if(user == null){
log.setStatus(LoginStatusEnum.FAIL.value());
log.setCreatorName(login.getUsername());
sysLogLoginService.save(log);
throw new MyException(ErrorCode.ACCOUNT_PASSWORD_ERROR);
}
//密码错误
if(!PasswordUtils.matches(login.getPassword(), user.getPassword())){
log.setStatus(LoginStatusEnum.FAIL.value());
log.setCreator(user.getId());
log.setCreatorName(user.getUsername());
sysLogLoginService.save(log);
throw new MyException(ErrorCode.ACCOUNT_PASSWORD_ERROR);
}
//账号停用
if(user.getStatus() == UserStatusEnum.DISABLE.value()){
log.setStatus(LoginStatusEnum.LOCK.value());
log.setCreator(user.getId());
log.setCreatorName(user.getUsername());
sysLogLoginService.save(log);
throw new MyException(ErrorCode.ACCOUNT_DISABLE);
}
//登录成功
log.setStatus(LoginStatusEnum.SUCCESS.value());
log.setCreator(user.getId());
log.setCreatorName(user.getUsername());
sysLogLoginService.save(log);
//生成token及判断token是否有效 (与管理端的token保持一致)
return sysUserTokenService.createToken(user.getId());
}
@PostMapping("logout")
@ApiOperation(value = "退出")
public Result logout(HttpServletRequest request) {
UserDetail user = SecurityUser.getUser();
//退出
sysUserTokenService.logout(user.getId());
//用户信息
SysLogLoginEntity log = new SysLogLoginEntity();
log.setOperation(LoginOperationEnum.LOGOUT.value());
log.setIp(IpUtils.getIpAddr(request));
log.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
log.setIp(IpUtils.getIpAddr(request));
log.setStatus(LoginStatusEnum.SUCCESS.value());
log.setCreator(user.getId());
log.setCreatorName(user.getUsername());
log.setCreateDate(new Date());
sysLogLoginService.save(log);
//退出登录时,删除redis缓存中的token
userDetailRedis.logout(SecurityUser.getUser().getId());
return new Result();
}
}以上代码包括两个函数,一个是login函数,另一个是logout函数。其中login函数用来处理登录请求,logout函数用来处理退出登录请求。
@PostMapping注解表示该restful接口为POST请求。该方法首先验证请求数据的有效性,然后验证验证码是否正确。如果正确,则根据用户名获取用户信息,并进行密码验证、账号状态验证等逻辑处理。最后,调用sysUserTokenService.createToken方法创建用户的登录令牌,并返回结果。
public SysUserDTO getByUsername(String username) {
SysUserEntity entity = baseDao.getByUsername(username);
return ConvertUtils.sourceToTarget(entity, SysUserDTO.class);
}以上代码是根据用户名得到用户实体的service实现,调用的sql如下:
<select id="getByUsername" resultType="com.iqiqiqi.aiwithdoc.modules.sys.entity.SysUserEntity">
select * from sys_user where username = #{value}
</select>@PostMapping("logout") 声明该restful接口为post请求,sysUserTokenService.logout(userId); 代码表示调用sysUserTokenService的logout方法更新token,参数userId为用户userId。
@Service
public class SysUserTokenServiceImpl extends BaseServiceImpl<SysUserTokenDao, SysUserTokenEntity> implements SysUserTokenService {
/**
* 48小时后过期
*/
private final static int EXPIRE = 3600 * 48;
@Resource
private SysUserService sysUserService;
@Resource
private UserDetailRedis userDetailRedis;
@Override
public Result createToken(Long userId) {
//用户token
String token;
//当前时间
Date now = new Date();
//过期时间
long expire = now.getTime() + EXPIRE * 1000;
Date expireTime = new Date(expire);
//判断是否生成过token
SysUserTokenEntity tokenEntity = baseDao.getByUserId(userId);
if(tokenEntity == null){
//生成一个token
token = TokenGenerator.generateValue();
tokenEntity = new SysUserTokenEntity();
tokenEntity.setUserId(userId);
tokenEntity.setToken(token);
tokenEntity.setUpdateDate(now);
tokenEntity.setExpireDate(expireTime);
//保存token
this.insert(tokenEntity);
}else{
//判断token是否过期
if(tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()){
//token过期,重新生成token
token = TokenGenerator.generateValue();
}else {
token = tokenEntity.getToken();
}
tokenEntity.setToken(token);
tokenEntity.setUpdateDate(now);
tokenEntity.setExpireDate(expireTime);
//更新token
this.updateById(tokenEntity);
}
//用户信息
SysUserDTO user = sysUserService.get(userId);
//保存到Redis
UserDetail userDetail = ConvertUtils.sourceToTarget(user, UserDetail.class);
userDetailRedis.set(userDetail, expire);
Map<String, Object> map = new HashMap<>(2);
map.put(Constant.TOKEN_HEADER, token);
map.put("expire", EXPIRE);
return new Result().ok(map);
}
@Override
public void logout(Long userId) {
//生成一个token
String token = TokenGenerator.generateValue();
//修改token
baseDao.updateToken(userId, token);
}
}logout方法,用于重新生成新的token,并保存在系统用户Token表sys_user_token中。该方法首先调用TokenGenerator中的generateValue方法生成随机的UUID,经过格式处理后形成新的token。最后,通过用户userId将token保存更新到数据库系统用户Token表中。
createToken方法,用于用户时生成token进行验证,过期时间为48小时。该方法首先根据用户userId查询系统用户Token表获取token,如果token为空,则生成新的token,保存在系统用户Token表中;如果token不为空,将token与当前时间比较是否过期。(若token过期需要重新生成并保存数据表中)最后将token和过期时间expire返回。
实现与手机端巡检功能,实际上是接受前端的请求,把数据处理保存到数据表,然后返回前端页面,供前端页面查看。以下是controller代码:
@PostMapping("saveRoutineCheckRecord")
@RequiresPermissions("recordmgt:routinecheckrecord:save")
public Result saveRoutineCheckRecord(@RequestBody RoutineCheckRecordDTO dto){
//效验数据
ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class);
routineCheckRecordService.saveRoutineCheckRecord(dto);
return new Result();
}根据注解可以看出,此restful接口为post请求,另外一个注解是进行权限判断。函数体中是调用对应的service实现保存巡检数据功能,以下是service层代码。
/**
* 1.保存 巡检记录实时表数据 tb_routine_check_record_realtime
* 2.保存 巡检记录表数据 tb_routine_check_record
* 从 定时(举例:每30秒)保存到 tb_routine_check_record_realtime实时表数据中,筛选出:
* (1) 同一个巡检点(pointId),同一巡检日期(inspectionDate),最早打卡时间(clockInTime)的记录
* (2) 同一个训检点(pointId),同一巡检日期(inspectionDate),第一次上报内容(reportContent)的记录
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void saveRoutineCheckRecord(RoutineCheckRecordDTO dto) {
//巡检进入巡检点打卡范围时:手机端定时插入巡检数据
//巡检日期
dto.setInspectionDate(new Date());
//登录用户信息
UserDetail user = SecurityUser.getUser();
//巡检人
if(user != null){
dto.setCurrentInspector(user.getUsername());
}
//1.保存巡检记录实时表 数据
RoutineCheckRecordRealtimeDTO recordRealtimeDTO = new RoutineCheckRecordRealtimeDTO();
BeanUtils.copyProperties(dto, recordRealtimeDTO);
routineCheckRecordRealtimeService.save(recordRealtimeDTO);
//2.保存 巡检记录表
// 查询最早打卡记录
Map<String,Object> params = new HashMap<>();
params.put("pointId",dto.getPointId());
params.put("inspectionDate",dto.getInspectionDate());
params.put("currentInspector",dto.getCurrentInspector());
int clockInCount = baseDao.getCountClockInData(params);
// 根据pointId,获取巡检点的编号
RoutineCheckPointDTO pointDTO = routineCheckPointService.get(dto.getPointId());
// 自动生成巡检记录编号oddNumbers
String oddNumbers = this.generateOddNumbers("XJ", pointDTO.getCode());
dto.setOddNumbers(oddNumbers);
if(clockInCount < 1){ //没有打卡记录
this.save(dto);
}
//上报内容不为空
if(StringUtils.isNotEmpty(dto.getReportContent())){
//查询最早上报内容记录
params.put("recordType",3); //数据记录类型 (3:上报)
RoutineCheckRecordDTO firstReportRecord = baseDao.getFirstReportData(params);
//存在最早上报记录,需要更新上报内容
if(firstReportRecord != null){
firstReportRecord.setReportContent(dto.getReportContent());
firstReportRecord.setClockInTime(dto.getClockInTime());
this.update(firstReportRecord);
}else{ //不存在,则新增记录
dto.setClockInTime(new Date());
this.save(dto);
}
}
}1.Transactional为数据库事务注解,接口出现异常时用于回滚插入数据。
2.首先是设置巡检日期为当前日期,获取当前登录用户信息,设置巡检人为当前用户, 保存巡检记录实时表tb_routine_check_record_realtime 数据。
3.然后根据当前巡检点pointId,当前巡检人currentInspector,巡检日期inspectionDate,查询巡检记录表tb_routine_check_record:若没有打卡记录,查询巡检点编号,同时调用generateOddNumbers方法生成巡检记录编号oddNumbers,保存数据到数据表;如果存在打卡记录,参照 下面第4条。
4.上述第3条查询,存在打卡记录,且参数reportContent不为空:查询最早上报内容的巡检记录firstReportRecord。如果firstReportRecord为空,不存在最早的上报记录,则新增保存巡检记录;如果firstReportRecord不为为空,讲上报内容reportContent和打卡时间colockInTime赋值,更新firstReportRecord到巡检记录表tb_routine_check_record中。
getCountClockInData方法:指定查询打卡记录数量,参数为Map集合params。
getFirstReportData方法:指定查询最早上报记录,参数为Map集合params。
@Mapper
public interface RoutineCheckRecordDao extends BaseDao<RoutineCheckRecordEntity> {
//指定查询 打卡记录数量
int getCountClockInData(Map<String, Object> params);
//指定查询 最早上报记录
RoutineCheckRecordDTO getFirstReportData(Map<String, Object> params);
}指定数据查询,并返回结果。
<!-- 指定查询 打卡记录数量 -->
<select id="getCountClockInData" resultType="int">
SELECT count(*)
FROM tb_routine_check_record t1
WHERE
t1.point_id = #{pointId}
<if test="inspectionDate != null">
<![CDATA[
AND t1.inspection_date = STR_TO_DATE(#{inspectionDate},'%Y-%m-%d')
]]>
</if>
AND t1.current_inspector = #{currentInspector}
</select>
<!-- 指定查询 最早上报记录 -->
<select id="getFirstReportData" resultType="com.chs.rcs.modules.recordmgt.dto.RoutineCheckRecordDTO">
SELECT t1.*
FROM tb_routine_check_record t1
WHERE
t1.point_id = #{pointId}
<if test="inspectionDate != null">
<![CDATA[
AND t1.inspection_date = STR_TO_DATE(#{inspectionDate},'%Y-%m-%d')
]]>
</if>
AND t1.current_inspector = #{currentInspector}
AND t1.record_type = #{recordType}
</select>扫码关注不迷路!!!
郑州升龙商业广场B座25层
service@iqiqiqi.cn
联系电话:187-0363-0315
联系电话:199-3777-5101