rev: 在线用户、强制用户下线、对接日志

This commit is contained in:
2023-08-03 14:50:13 +08:00
parent 373670a51d
commit 13c1cdb824
35 changed files with 315 additions and 79 deletions

View File

@@ -49,11 +49,11 @@ public class AuthorizationController {
@ApiOperation("登录授权")
@PostMapping(value = "/login")
public ResponseEntity<Object> login(@RequestBody Map authMap) throws Exception {
public ResponseEntity<Object> login(@RequestBody Map authMap, HttpServletRequest request) throws Exception {
if (ObjectUtil.isEmpty(authMap)){
return ResponseEntity.noContent().build();
}
return ResponseEntity.ok(onlineUserService.login(authMap));
return ResponseEntity.ok(onlineUserService.login(authMap, request));
}

View File

@@ -15,10 +15,12 @@
*/
package org.nl.system.controller.user;
import com.alibaba.fastjson.JSONArray;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nl.common.utils.EncryptUtils;
import org.nl.system.service.secutiry.impl.OnlineUserService;
import org.nl.system.service.user.dto.OnlineUserDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
@@ -27,6 +29,7 @@ import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
@@ -57,10 +60,10 @@ public class OnlineController {
@ApiOperation("踢出用户")
@DeleteMapping
// @SaCheckPermission("@el.check()")
public ResponseEntity<Object> delete(@RequestBody Set<String> keys) throws Exception {
for (String key : keys) {
public ResponseEntity<Object> delete(@RequestBody List<OnlineUserDto> keys) throws Exception {
for (OnlineUserDto key : keys) {
// 解密Key
key = EncryptUtils.desDecrypt(key);
key.setKey(EncryptUtils.desDecrypt(key.getKey()));
onlineUserService.kickOut(key);
}
return new ResponseEntity<>(HttpStatus.OK);

View File

@@ -65,4 +65,6 @@ public interface ISysDeptService extends IService<SysDept> {
void createDept(SysDept dept);
List<SysDept> getUserDeptByUserId(String userId);
}

View File

@@ -41,4 +41,6 @@ public interface SysDeptMapper extends BaseMapper<SysDept> {
* @return
*/
String findAllChild(String pid);
List<SysDept> getUserDeptByUserId(String userId);
}

View File

@@ -37,4 +37,17 @@
</foreach>
)
</select>
<select id="getUserDeptByUserId" resultType="org.nl.system.service.dept.dao.SysDept">
SELECT
sd.*
FROM
sys_dept sd
WHERE sd.dept_id IN (
SELECT
d.dept_id
FROM
`sys_user_dept` d
WHERE d.user_id = #{userId}
) AND sd.is_used = TRUE
</select>
</mapper>

View File

@@ -176,4 +176,9 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
sysDeptMapper.updateSubCount(dept.getPid());
}
}
@Override
public List<SysDept> getUserDeptByUserId(String userId) {
return sysDeptMapper.getUserDeptByUserId(userId);
}
}

View File

@@ -18,9 +18,11 @@ package org.nl.system.service.secutiry.impl;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@@ -28,6 +30,8 @@ import org.nl.common.utils.*;
import org.nl.config.RsaProperties;
import org.nl.common.exception.BadRequestException;
import org.nl.common.utils.dto.CurrentUser;
import org.nl.system.service.dept.ISysDeptService;
import org.nl.system.service.dept.dao.SysDept;
import org.nl.system.service.secutiry.dto.UserDto;
import org.nl.system.service.role.ISysRoleService;
import org.nl.system.service.secutiry.dto.AuthUserDto;
@@ -55,6 +59,8 @@ public class OnlineUserService {
@Autowired
private ISysUserService sysUserService;
@Autowired
private ISysDeptService deptService;
@Autowired
private ISysRoleService roleService;
@Autowired
private RedisUtils redisUtils;
@@ -69,20 +75,27 @@ public class OnlineUserService {
* @param token /
* @param request /
*/
public void save(UserDto userDto, String token, HttpServletRequest request){
// String dept = userDto.getDept().getName();
String dept = "";
public void save(SysUser userDto, String token, HttpServletRequest request){
// 获取用户部门
List<SysDept> userDeptByUserId = deptService.getUserDeptByUserId(userDto.getUser_id());
StringBuilder sb = new StringBuilder();
for (SysDept dept : userDeptByUserId) {
sb.append(dept.getName()).append("");
}
if (sb.length() > 0) {
sb.setLength(sb.length() - 1);
}
String dept = sb.toString();
String ip = StringUtils.getIp(request);
String browser = StringUtils.getBrowser(request);
// String address = StringUtils.getCityInfo(ip);
String address = "局域网";
String address = StringUtils.getCityInfo(ip);
OnlineUserDto onlineUserDto = null;
try {
// onlineUserDto = new OnlineUserDto(userDto.getUsername(), userDto.getNickName(), dept, browser , ip, address, EncryptUtils.desEncrypt(token), new Date());
onlineUserDto = new OnlineUserDto(userDto.getUsername(), userDto.getPerson_name(), dept, browser , ip, address, EncryptUtils.desEncrypt(token), new Date());
} catch (Exception e) {
log.error(e.getMessage(),e);
}
redisUtils.set(token, onlineUserDto, StpUtil.getTokenTimeout());
redisUtils.set("oline-" + userDto.getUsername(), onlineUserDto, StpUtil.getTokenTimeout());
}
/**
@@ -109,7 +122,7 @@ public class OnlineUserService {
Collections.reverse(keys);
List<OnlineUserDto> onlineUserDtos = new ArrayList<>();
for (String key : keys) {
if (key.length() == 1511) {
if (key.startsWith("oline-")) {
OnlineUserDto onlineUserDto = (OnlineUserDto) redisUtils.get(key);
if(StrUtil.isNotEmpty(filter)){
if(onlineUserDto.toString().contains(filter)){
@@ -127,10 +140,26 @@ public class OnlineUserService {
/**
* 踢出用户
* @param key /
* @param key: OnlineUserDto /
*/
public void kickOut(String key){
public void kickOut(OnlineUserDto key) {
// 获取用户
SysUser one = sysUserService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, key.getUserName()));
if (ObjectUtil.isNotEmpty(one)) {
redisUtils.del("oline-" + one.getUsername());
}
// 下线
StpUtil.logoutByTokenValue(key.getKey()); // 通过token强退
}
/**
* 踢出用户
* @param keytoken /
*/
public void kickOut(String key) {
redisUtils.del(key);
// 下线
StpUtil.logoutByTokenValue(key); // 通过token强退
}
/**
@@ -211,7 +240,7 @@ public class OnlineUserService {
}
}
@SneakyThrows
public Map<String, Object> login(Map paramMap){
public Map<String, Object> login(Map paramMap, HttpServletRequest request){
// 密码解密 - 前端的加密规则: encrypt
AuthUserDto authUser = JSON.toJavaObject((JSON) JSON.toJSON(paramMap), AuthUserDto.class);
String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
@@ -263,7 +292,7 @@ public class OnlineUserService {
put("user", user);
}};
// 保存在线信息
// onlineUserService.save(userDto, StpUtil.getTokenValue(), request);
this.save(userInfo, StpUtil.getTokenValue(), request);
return authInfo;
}
}

View File

@@ -1,6 +1,5 @@
package org.nl.system.service.user;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import org.nl.common.domain.query.PageQuery;
import org.nl.system.service.user.dao.SysUser;

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import org.nl.common.domain.query.PageQuery;
import com.baomidou.mybatisplus.extension.service.IService;
import org.nl.wms.das.devicecheck.service.dao.DasDeviceCheckRecord;
import org.nl.wms.pda.service.dao.vo.PdaResponseVo;
import java.util.Map;
import java.util.Set;
@@ -27,7 +28,7 @@ public interface IDasDeviceCheckRecordService extends IService<DasDeviceCheckRec
* 创建
* @param entity /
*/
void create(DasDeviceCheckRecord entity);
PdaResponseVo create(DasDeviceCheckRecord entity);
/**
* 编辑

View File

@@ -17,6 +17,7 @@ import org.nl.system.service.user.dao.SysUser;
import org.nl.wms.das.devicecheck.service.IDasDeviceCheckRecordService;
import org.nl.wms.das.devicecheck.service.dao.mapper.DasDeviceCheckRecordMapper;
import org.nl.wms.das.devicecheck.service.dao.DasDeviceCheckRecord;
import org.nl.wms.pda.service.dao.vo.PdaResponseVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -46,7 +47,7 @@ public class DasDeviceCheckRecordServiceImpl extends ServiceImpl<DasDeviceCheckR
}
@Override
public void create(DasDeviceCheckRecord entity) {
public PdaResponseVo create(DasDeviceCheckRecord entity) {
SysUser sysUser = userService.getOne(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUsername, entity.getUsername()));
String now = DateUtil.now();
@@ -54,6 +55,7 @@ public class DasDeviceCheckRecordServiceImpl extends ServiceImpl<DasDeviceCheckR
entity.setRecord_time(now);
entity.setPerson_name(sysUser.getPerson_name());
dasDeviceCheckRecordMapper.insert(entity);
return PdaResponseVo.pdaResultOk("设备[" + entity.getDevice_code() + "]点检成功,点检人: " + sysUser.getPerson_name());
}
@Override

View File

@@ -5,12 +5,9 @@ import org.nl.wms.ext.acs.service.dto.to.BaseResponse;
/**
* @Author: lyd
* @Description:
* @Description: acs请求wms
* @Date: 2023/6/26
*/
public interface AcsToWmsService {
BaseResponse acsApply(JSONObject param);
}

View File

@@ -0,0 +1,9 @@
package org.nl.wms.ext.acs.service;
/**
* @Author: lyd
* @Description: mes请求wms
* @Date: 2023/8/3
*/
public interface MesToWmsService {
}

View File

@@ -0,0 +1,9 @@
package org.nl.wms.ext.acs.service;
/**
* @Author: lyd
* @Description: wms请求acs
* @Date: 2023/8/3
*/
public interface WmsToMesService {
}

View File

@@ -129,7 +129,7 @@ public class AcsToWmsServiceImpl implements AcsToWmsService {
NoticeTypeEnum.EXCEPTION.getCode());
}
// acs对接记录
interactRecordService.saveRecord(param, result, GeneralDefinition.ACS_LMS);
interactRecordService.saveRecord(param.getString("request_medthod_name"), param, result, GeneralDefinition.ACS_LMS);
return result;
}

View File

@@ -0,0 +1,15 @@
package org.nl.wms.ext.acs.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.nl.wms.ext.acs.service.MesToWmsService;
import org.springframework.stereotype.Service;
/**
* @Author: lyd
* @Description:
* @Date: 2023/8/3
*/
@Slf4j
@Service
public class MesToWmsServiceImpl implements MesToWmsService {
}

View File

@@ -7,14 +7,12 @@ import org.nl.wms.ext.acs.service.WmsToAcsService;
import org.nl.wms.ext.acs.service.dto.ResultForAcs;
import org.nl.wms.ext.acs.service.dto.to.acs.PutActionRequest;
import org.nl.wms.ext.record.service.ISysInteractRecordService;
import org.nl.wms.pdm.workorder.service.dao.PdmBdWorkorder;
import org.nl.wms.pdm.workorder.service.dao.vo.AcsWorkOrderVo;
import org.nl.wms.sch.task_manage.GeneralDefinition;
import org.nl.wms.sch.task_manage.task.AcsUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -43,7 +41,7 @@ public class WmsToAcsServiceImpl implements WmsToAcsService {
resultForAcs.setMessage(e.getMessage());
}
// 记录日志
interactRecordService.saveRecord(workorder, resultForAcs, GeneralDefinition.LMS_ACS);
interactRecordService.saveRecord("下发工单", workorder, resultForAcs, GeneralDefinition.LMS_ACS);
return resultForAcs;
}
@@ -60,7 +58,7 @@ public class WmsToAcsServiceImpl implements WmsToAcsService {
resultForAcs.setMessage(e.getMessage());
}
// 记录日志
// interactRecordService.saveRecord(workorder, resultForAcs, GeneralDefinition.LMS_ACS);
interactRecordService.saveRecord("下发信号传送空木托盘", list, resultForAcs, GeneralDefinition.LMS_ACS);
return resultForAcs;
}
}

View File

@@ -0,0 +1,15 @@
package org.nl.wms.ext.acs.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.nl.wms.ext.acs.service.WmsToMesService;
import org.springframework.stereotype.Service;
/**
* @Author: lyd
* @Description:
* @Date: 2023/8/3
*/
@Slf4j
@Service
public class WmsToMesServiceImpl implements WmsToMesService {
}

View File

@@ -61,10 +61,10 @@ public interface ISysInteractRecordService extends IService<SysInteractRecord> {
/**
*
* @param code
* @param name
* @param request
* @param response
* @param direction
*/
<T> void saveRecord(String code, Object request, T response, String direction);
<K, V> void saveRecord(String name, K request, V response, String direction);
}

View File

@@ -25,8 +25,8 @@ public class SysInteractRecord implements Serializable {
@ApiModelProperty(value = "对接标识")
private String interact_id;
@ApiModelProperty(value = "对接编码")
private String interact_code;
@ApiModelProperty(value = "对接名称")
private String interact_name;
@ApiModelProperty(value = "响应编码")
private int code;

View File

@@ -16,7 +16,6 @@ import org.nl.wms.ext.acs.service.dto.to.BaseResponse;
import org.nl.wms.ext.record.service.ISysInteractRecordService;
import org.nl.wms.ext.record.service.dao.mapper.SysInteractRecordMapper;
import org.nl.wms.ext.record.service.dao.SysInteractRecord;
import org.nl.wms.pdm.workorder.service.dao.PdmBdWorkorder;
import org.nl.wms.pdm.workorder.service.dao.vo.AcsWorkOrderVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -70,7 +69,7 @@ public class SysInteractRecordServiceImpl extends ServiceImpl<SysInteractRecordM
public void saveRecord(Object param, BaseResponse response, String direction) {
SysInteractRecord entity = new SysInteractRecord();
entity.setInteract_id(IdUtil.getSnowflake(1, 1).nextIdStr());
entity.setInteract_code(response.getRequestNo());
entity.setInteract_name(response.getRequestNo());
entity.setCode(response.getCode());
entity.setMessage(response.getMessage());
entity.setRecord_time(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS"));
@@ -85,7 +84,7 @@ public class SysInteractRecordServiceImpl extends ServiceImpl<SysInteractRecordM
public void saveRecord(AcsWorkOrderVo workorder, ResultForAcs resultForAcs, String lmsAcs) {
SysInteractRecord entity = new SysInteractRecord();
entity.setInteract_id(IdUtil.getSnowflake(1, 1).nextIdStr());
entity.setInteract_code(workorder.getWorkorder_code());
entity.setInteract_name(workorder.getWorkorder_code());
entity.setCode(resultForAcs.getStatus());
entity.setMessage(resultForAcs.getMessage());
entity.setRecord_time(DateUtil.now());
@@ -97,23 +96,22 @@ public class SysInteractRecordServiceImpl extends ServiceImpl<SysInteractRecordM
}
@Override
public <T> void saveRecord(String code, Object request, T response, String direction) {
public <K, V> void saveRecord(String name, K request, V response, String direction) {
SysInteractRecord entity = new SysInteractRecord();
entity.setInteract_id(IdUtil.getSnowflake(1, 1).nextIdStr());
entity.setRecord_time(DateUtil.now());
entity.setRecord_time(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS"));
entity.setInteract_name(name);
entity.setDirection(direction);
entity.setRequest_param(JSONObject.toJSONString(request));
entity.setResponse_param(JSONObject.toJSONString(response));
if (response instanceof BaseResponse) {
BaseResponse re = (BaseResponse) response;
entity.setInteract_code(re.getRequestNo());
entity.setCode(re.getCode());
entity.setMessage(re.getMessage());
entity.setIs_success(re.getCode() == HttpStatus.HTTP_OK);
}
if (response instanceof ResultForAcs) {
ResultForAcs re = (ResultForAcs) response;
entity.setInteract_code(IdUtil.simpleUUID()); // todo: 暂定
entity.setCode(re.getStatus());
entity.setMessage(re.getMessage());
entity.setIs_success(re.getStatus() == HttpStatus.HTTP_OK);

View File

@@ -37,8 +37,7 @@ public class PdaController {
@ApiOperation("设备点检")
@SaIgnore
public ResponseEntity<Object> deviceCheck(@Validated @RequestBody DasDeviceCheckRecord entity){
deviceCheckRecordService.create(entity);
return new ResponseEntity<>(HttpStatus.OK);
return new ResponseEntity<>(deviceCheckRecordService.create(entity), HttpStatus.OK);
}
@PostMapping("/deviceCheck/deviceInfo")

View File

@@ -5,7 +5,7 @@
<select id="getDeviceInfo" resultType="org.nl.wms.pda.service.dao.vo.DropdownListVo">
SELECT
p.point_code AS `value`,
p.point_name AS label
p.point_name AS text
FROM
`sch_base_point` p
WHERE p.region_code = 'YZ' AND p.point_type = '1' AND p.point_code = p.parent_point_code
@@ -13,7 +13,7 @@
<select id="getDictByCode" resultType="org.nl.wms.pda.service.dao.vo.DropdownListVo">
SELECT
`value`,
label
label AS text
FROM
`sys_dict`
WHERE `code` = #{code}

View File

@@ -12,5 +12,5 @@ import java.io.Serializable;
@Data
public class DropdownListVo implements Serializable {
private String value;
private String label;
private String text;
}

View File

@@ -0,0 +1,20 @@
package org.nl.wms.pda.service.dao.vo;
import lombok.Data;
/**
* @Author: lyd
* @Description: 手持返回
* @Date: 2023/8/3
*/
@Data
public class PdaResponseVo {
// 先提供一个message
private String message;
public static PdaResponseVo pdaResultOk(String message) {
PdaResponseVo vo = new PdaResponseVo();
vo.setMessage(message);
return vo;
}
}

View File

@@ -40,9 +40,15 @@
0 AS b,
0 AS h,
0 AS w,
0 AS `size`,
0 AS size_error,
0 AS single_weight,
0 AS drawing_address
0 AS drawing_address,
0 AS standard_size1,
0 AS standard_size2,
0 AS standard_size3,
0 AS standard_size4,
0 AS standard_weight,
0 AS detection_error
FROM
`pdm_bd_workorder` w
LEFT JOIN md_base_material m ON m.material_id = w.material_id

View File

@@ -17,7 +17,13 @@ public class AcsWorkOrderVo {
private String b;
private String h;
private String w;
private String size; // 尺寸允许误差
private String size_error; // 尺寸允许误差
private String single_weight; // 单重允许误差
private String drawing_address; // 图纸地址
private String standard_size_height1; // 标准尺寸1
private String standard_size_height2; // 标准尺寸2
private String standard_size_height3; // 标准尺寸3
private String standard_size_height4; // 标准尺寸4
private String standard_weight; // 标准重量
private String detection_error; // 检测误差值
}

View File

@@ -4,10 +4,13 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.nl.common.exception.BadRequestException;
import org.nl.wms.ext.acs.service.dto.ResultForAcs;
import org.nl.wms.ext.record.service.ISysInteractRecordService;
import org.nl.wms.pdm.workorder.service.IPdmBdWorkorderService;
import org.nl.wms.pdm.workorder.service.dao.PdmBdWorkorder;
import org.nl.wms.sch.point.service.ISchBasePointService;
@@ -33,6 +36,7 @@ import java.util.List;
* @date 2023年05月16日 16:42
* @desc desc
*/
@Slf4j
@Component
public abstract class AbstractTask {
@Autowired
@@ -43,7 +47,8 @@ public abstract class AbstractTask {
private ISchBasePointService pointService;
@Autowired
private IPdmBdWorkorderService workorderService;
@Autowired
private ISysInteractRecordService interactRecordService;
/**
* 任务创建
@@ -72,6 +77,7 @@ public abstract class AbstractTask {
* @created 2020年6月12日 下午5:52:28
*/
protected ResultForAcs renotifyAcs(List<SchBaseTask> taskList) {
ResultForAcs resultForAcs = ResultForAcs.requestOk();
//1、获取任务
//2、根据任务配置补全任务
//3、下发
@@ -97,8 +103,17 @@ public abstract class AbstractTask {
this.setTask(task.getConfig_code(), taskDto);
list.add(taskDto);
}
return AcsUtil.notifyAcs("api/wms/task", list);
try {
resultForAcs = AcsUtil.notifyAcs("api/wms/task", list);
} catch (Exception e) {
log.error("任务下发异常: {}", e.getMessage());
resultForAcs.setTimestamp(DateUtil.now());
resultForAcs.setStatus(HttpStatus.HTTP_BAD_REQUEST);
resultForAcs.setMessage(e.getMessage());
}
// 记录日志
interactRecordService.saveRecord("下发任务", list, resultForAcs, GeneralDefinition.LMS_ACS);
return resultForAcs;
}
private AcsTaskDto setTask(String config_code, AcsTaskDto taskDto) {

View File

@@ -13,6 +13,7 @@ import org.nl.wms.sch.task.service.ISchBaseTaskconfigService;
import org.nl.wms.sch.task.service.dao.SchBaseTask;
import org.nl.wms.sch.task.service.dao.SchBaseTaskconfig;
import org.nl.wms.sch.task_manage.AbstractTask;
import org.nl.wms.sch.task_manage.GeneralDefinition;
import org.nl.wms.sch.task_manage.enums.NoticeTypeEnum;
import org.nl.wms.sch.task_manage.enums.PointStatusEnum;
import org.nl.wms.sch.task_manage.enums.TaskFinishedTypeEnum;

View File

@@ -20,6 +20,7 @@ import org.nl.wms.sch.task.service.ISchBaseTaskconfigService;
import org.nl.wms.sch.task.service.dao.SchBaseTask;
import org.nl.wms.sch.task.service.dao.SchBaseTaskconfig;
import org.nl.wms.sch.task_manage.AbstractTask;
import org.nl.wms.sch.task_manage.GeneralDefinition;
import org.nl.wms.sch.task_manage.enums.NoticeTypeEnum;
import org.nl.wms.sch.task_manage.enums.PointStatusEnum;
import org.nl.wms.sch.task_manage.enums.TaskFinishedTypeEnum;