02-瑞吉外卖员工表的增删改查

添加员工信息

执行流程

第一步: 用户点击添加员工按钮跳转到add.html页面,然后在页面中输入要添加的员工的信息

第二步: 用户点击保存按钮发送Ajax请求将用户输入的员工信息以json的格式提交到服务端

第三步: 服务端Controller接收页面提交的json格式的数据并转化为java对象, 通过Service调用Mapper将员工信息插入到employee表中,最后向前端返回通用结果类

  • id: 由雪花算法/自动递增机制自动生成
  • username: 设置唯一性约束, 员工的的登录账户必须是唯一的
  • password: 默认指定一个密码(身份证后六位,123456等),但是这个密码不能直接在数据库中设为默认值,因为数据库设置的默认值无法加密
  • status: 设定员工的状态,默认值1表示启用(正常),0表示禁用,直接在数据库中设置默认值不需要加密
  • createTime/updateTime: 创建/更新员工信息的时间,这个就指定当前时间就好了
  • createUser/updateUser: 创建/更新员工信息的管理员ID,依靠这个可以溯源避免出现莫名的员工账号

在这里插入图片描述

前端页面

add.html为公共页面,新增员工和编辑员工都是在此页面操作,只不过添加员工时还会显示保存并继续添加按钮

<script>// add.html页面提交的数据模型 ruleForm : {'name': '','phone': '','sex': '男','idNumber': '',username: ''}
</script><el-formref="ruleForm":model="ruleForm":rules="rules":inline="false"label-width="180px"class="demo-ruleForm"><el-form-item label="账号:" prop="username"><el-input v-model="ruleForm.username" placeholder="请输入账号" maxlength="20"/></el-form-item><el-form-itemlabel="员工姓名:"prop="name"><el-inputv-model="ruleForm.name"placeholder="请输入员工姓名"maxlength="20"/></el-form-item><el-form-itemlabel="手机号:"prop="phone"><el-inputv-model="ruleForm.phone"placeholder="请输入手机号"maxlength="20"/></el-form-item><el-form-itemlabel="性别:"prop="sex"><el-radio-group v-model="ruleForm.sex"><el-radio label=""></el-radio><el-radio label=""></el-radio></el-radio-group></el-form-item><el-form-itemlabel="身份证号:"prop="idNumber"><el-inputv-model="ruleForm.idNumber"placeholder="请输入身份证号"maxlength="20"/></el-form-item><div class="subBox address"><el-form-item><el-button  @click="goBack()">取消</el-button><el-buttontype="primary"<!--发送ajax请求将用户输入的员工信息以json的格式提交到服务端-->@click="submitForm('ruleForm', false)">保存</el-button><el-button<!--如果是添加员工显示该按钮,如果是修改员工则不显示该按钮-->v-if="actionType == 'add'"type="primary"class="continue"@click="submitForm('ruleForm', true)">保存并继续添加</el-button></el-form-item></div>
</el-form>

submitForm函数调用addEmployee函数发送Ajax请求将用户输入的员工信息以json的格式提交到服务端,最后接收服务器响应的通用结果类执行Ajax的回调函数

submitForm (formName, st) {this.$refs[formName].validate((valid) => {// 对表单的数据进行校验if (valid) {// 判断是添加还是修改if (this.actionType === 'add') {const params = {...this.ruleForm,sex: this.ruleForm.sex === '女' ? '0' : '1'// 将表单中的男女转换为数据库中对应的0或1}addEmployee(params).then(res => {// 接收服务器Controller响应的结果if (res.code === 1) {this.$message.success('员工添加成功!')if (!st) {this.goBack()} else {// 员工添加失败this.ruleForm = {username: '','name': '','phone': '',// 'password': '',// 'rePassword': '',/'sex': '男','idNumber': ''}}} else {this.$message.error(res.msg || '操作失败')}}).catch(err => {this.$message.error('请求出错了:' + err)})} else {// 修改员工信息......}} else {console.log('error submit!!')return false}})
}// 发起ajax请求新增添加员工
function addEmployee (params) {return $axios({url: '/employee',method: 'post',data: { ...params }})
} 

后端处理请求

处理请求路径为/employee的POST请求将添加的员工信息保存到数据库

@RestController
@RequestMapping("/employee")
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;@PostMappingpublic Result<String> save(HttpServletRequest request, @RequestBody Employee employee) {// 接收的employee对象的id,password,status,createTime属性都还是nulllog.info("新增的员工信息:{}", employee.toString());// 设置默认密码为123456,并采用MD5加密employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));// 设置createTime和updateTimeemployee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());// 在session中获取创建人的id设置CreateUser和UpdateUserLong empId = (Long) request.getSession().getAttribute("employee");employee.setCreateUser(empId);employee.setUpdateUser(empId);// 将employee对象存入数据库employeeService.save(employee);// 响应通用的结果封装类return Result.success("添加员工成功");}
}	

全局异常处理器

添加员工时存入相同的username(唯一)控制台会报java.sql.SQLIntegrityConstraintViolationException:Duplicate entry...,有两种方式捕获

  • 在Controller方法中对employeeService.save(employee)使用try...catch进行异常捕获,这种方式只能捕获当前代码发生的异常
  • 使用全局异常处理器可以捕获整个项目中指定类型的异常

创建一个全局异常处理类common/GlobalExceptionHandler,添加exceptionHandler方法用来捕获异常,并返回通用错误信息如提示用户名xxx已存在

@Slf4j
// 只有加了@Controller注解的类发生的异常才会捕获
@ControllerAdvice(annotations = {RestController.class,Controller.class})
@ResponseBody	
public class GlobalExceptionHandler {// 拦截SQLIntegrityConstraintViolationException异常@ExceptionHandler(SQLIntegrityConstraintViolationException.class)public Result<String> exceptionHandler(SQLIntegrityConstraintViolationException exception) {// 控制台日志输出的异常信息为Duplicate entry 'zhangsan' for key 'employee.idx_username'log.error(exception.getMessage());// 如果异常信息包含Duplicate entry,则说明有条目重复if (exception.getMessage().contains("Duplicate entry")) {// 将字符串按照空格分割得到username(字符串格式是固定的)String[] split = exception.getMessage().split(" ");String username = split[2];// 拼串作为错误信息返回return Result.error("用户名" + username + "已存在");}// 其他错误信息return Result.error("未知错误");}
}

员工信息分页查询

执行流程

第一步: 在加载index.html页面时自动调用list.html中的creatued()函数发送ajax请求,将分页查询需要的参数page,pageSize,name提交到服务端

  • 如果当前就在index.html页面中可以通过指定查询条件发起Ajax请求查询分页数据, 请求参数除了携带page,pageSize外还携带name表示查询条件
  • 如果用户点击页数上下翻页的时候也会发起Ajax请求提交page,pageSize请求参数查询员工分页数据

在这里插入图片描述

第二步: 服务端Controller接收页面提交的参数,通过Service调用Mapper操作数据库查询分页数据,然后将查询到的分页数据响应给页面

第三步: 页面接收到分页数据并通过ElementUI的Table组件展示到页面上

在这里插入图片描述

前端页面

list.html页面携带分页查询需要的参数page,pageSize,name发起Ajax请求,然后将服务端的Controller响应的所有员工数据展示到页面

created() {this.init()this.user = JSON.parse(localStorage.getItem('userInfo')).username
},async init () {// json格式的请求参数const params = {page: this.page,pageSize: this.pageSize,name: this.input ? this.input : undefined}await getMemberList(params).then(res => {// 执行回调函数接收服务端Controller响应的所有员工数据if (String(res.code) === '1') {this.tableData = res.data.records || []this.counts = res.data.total}}).catch(err => {this.$message.error('请求出错了:' + err)})function getMemberList (params) {return $axios({url: '/employee/page',method: 'get',params})
}

request全局拦截器拦截get请求将json格式的请求参数转化为name=value的格式拼接到URL上

service.interceptors.request.use(config => {
// 是否需要在响应头中添加token
// const isToken = (config.headers || {}).isToken === false
// if (getToken() && !isToken) {
//   config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
// }
// get请求映射params参数
if (config.method === 'get' && config.params) {let url = config.url + '?';for (const propName of Object.keys(config.params)) {const value = config.params[propName];var part = encodeURIComponent(propName) + "=";if (value !== null && typeof(value) !== "undefined") {if (typeof value === 'object') {for (const key of Object.keys(value)) {let params = propName + '[' + key + ']';var subPart = encodeURIComponent(params) + "=";url += subPart + encodeURIComponent(value[key]) + "&";}} else {url += part + encodeURIComponent(value) + "&";}}}url = url.slice(0, -1);config.params = {};config.url = url;
}
return config
}, error => {console.log(error)Promise.reject(error)
})

list.html页面中展示数据的时候,将后端响应的Integer类型的status转化为已禁用或者正常的字符串

<el-table-column label="账号状态"><template slot-scope="scope">{{ String(scope.row.status) === '0' ? '已禁用' : '正常' }}</template>
</el-table-column>

后端处理请求

第一步: 在config/MybatisPlusConfig中添加MyBatisPlus的分页插件对象并交给IoC容器管理

@Configuration
//可以将主类中的扫描Mapper接口的注解移到此处
@MapperScan("com.atguigu.mybatisplus.mapper") 
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {// 配置MyBatis Plus中插件的对象MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 在插件对象中添加内部插件并设置数据库类型interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}

第二步: 查询所有的员工信息并进行分页,Page对象中封装了查询到的所有员工信息(records属性)和其他分页信息如total,size属性

@GetMapping("/page")
public Result<Page> page(int page, int pageSize, String name) {log.info("page={},pageSize={},name={}", page, pageSize, name);// 在分页对象中设置分页参数,当前页码数和每页显示的记录数Page<Employee> pageInfo = new Page<>(page, pageSize);// 构造条件构造器LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();// 添加过滤条件,当我们没有输入name查询条件时表示查询所有wrapper.like(!(name == null || "".equals(name)), Employee::getName, name);// wrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);// 添加排序条件,根据更新时间对查询的结果进行降序排序wrapper.orderByDesc(Employee::getUpdateTime);// 添加分页对象和条件构造器查询所有,最终数据都会被封装到分页对象中employeeService.page(pageInfo, wrapper);// 将Page对象封装到Result对象的data属性中,Page对象的records属性中封装了查询到的员工信息return Result.success(pageInfo);
}

启用/禁用员工账号

执行流程

第一步: 用户点击启用/禁用按钮时页面发送Ajax的PUT请求将参数元素所在行的id和status提交到服务端

第二步: 服务端Controller接收页面提交的数据通过Service调用Mapper操作数据库更新对应id员工的status的值

在这里插入图片描述

前端页面

只有管理员(admin)可以对其他所有员工账号进行启用、禁用操作, 普通用户登录系统后启用、禁用按钮不显示

  • 如果某个员工账号状态为正常则按钮显示为禁用,如果员工账号状态为已禁用则按钮显示为启用

当加载完list.html页面时获取一下当前登录账号的username并判断是不是admin,如果是就显示启用/禁用按钮否则不显示

<script>created() {this.init()this.user = JSON.parse(localStorage.getItem('userInfo')).username}
</script><el-buttontype="text"size="small"class="delBut non"@click="statusHandle(scope.row)"v-if="user === 'admin'"
>{{ scope.row.status == '1' ? '禁用' : '启用' }}
</el-button>

用户点完启用禁用按钮后调用statusHandle函数弹出提示窗口,点击确定之后调用enableOrDisableEmployee函数携带当前行的id值与!status发起PUT请求

statusHandle (row) {// row表示每行数据对应的json对象this.id = row.idthis.status = row.statusthis.$confirm('确认调整该账号的状态?', '提示', {'confirmButtonText': '确定','cancelButtonText': '取消','type': 'warning'}).then(() => {// 对当前状态进行取反操作实现切换禁用/启用状态,如果this.status为1则status为0,如果this.status为0则status为1enableOrDisableEmployee({ 'id': this.id, 'status': !this.status ? 1 : 0 }).then(res => {// 根据服务器响应的结果执行回调函数console.log('enableOrDisableEmployee',res)if (String(res.code) === '1') {this.$message.success('账号状态更改成功!')this.handleQuery()}}).catch(err => {this.$message.error('请求出错了:' + err)})})
}// 修改启用/禁用接口
function enableOrDisableEmployee (params) {return $axios({url: '/employee',method: 'put',data: { ...params }})
}

配置状态转换器

执行完分页查询请求服务器响应给页面的Employee对象的id是正确的,但JS将id渲染到页面上时会丢失精度,这样获取的页面id就和数据库中id有差异

JS对Long型数据进行处理时会丢失精度导致提交的id和数据库中的id不一致, 需要服务端在给页面响应json数据时将Long型数据统一转为String字符串

在这里插入图片描述

对象转换器:基于jackson将Java对象转为json[序列化Java对象到JSON],或者将json转为Java对象[从JSON反序列化Java对象]

第一步: 创建commom/JacksonObjectMapper继承ObjectMapper,不需要向工程中导入依赖

public class JacksonObjectMapper extends ObjectMapper {public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";public JacksonObjectMapper() {super();// 收到未知属性时不报异常this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);// 反序列化时,属性不存在的兼容处理this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);SimpleModule simpleModule = new SimpleModule().addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))).addSerializer(BigInteger.class, ToStringSerializer.instance)// 将Long类型转化为String类型.addSerializer(Long.class, ToStringSerializer.instance)// 将日期类型转化为日期格式的字符串.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));//注册功能模块 例如可以添加自定义序列化器和反序列化器this.registerModule(simpleModule);}
}

第二步: 扩展Mvc框架的消息转换器,在此消息转换器中使用自己的对象转换器进行java数据到json对象的转换

@Configuration
@Slf4j
public class WebMvcConfig extends WebMvcConfigurationSupport {@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");}@Overrideprotected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();// 设置自己的对象转化器,底层使用jackson将java对象转为jsonmessageConverter.setObjectMapper(new JacksonObjectMapper());// 将自己的消息转换器对象追加到mvc框架的转换器集合当中(index设置为0,表示设置在第一个位置,避免被其它转换器接收,从而达不到想要的功能)converters.add(0, messageConverter);}
}

后端处理请求

更新员工记录的status状态字段的值,该方法也可以修改员工信息的其他字段

@PutMapping
public Result<String> update(@RequestBody Employee employee, HttpServletRequest request) {log.info(employee.toString());Long id = (Long) request.getSession().getAttribute("employee");employee.setUpdateUser(id);employee.setUpdateTime(LocalDateTime.now());employeeService.updateById(employee);return Result.success("员工信息修改成功");
}

编辑员工信息

执行流程

第一步用户点击编辑按钮跳转到add.html公共页面中并在URL中携带编辑员工的id作为请求参数,如果URL中没有携带id表示添加操作

在这里插入图片描述

<el-buttontype="text"size="small"class="blueBug"@click="addMemberHandle(scope.row.id)":class="{notAdmin:user !== 'admin'}"
>编辑
</el-button>
<script>addMemberHandle (st) {if (st === 'add'){window.parent.menuHandle({id: '2',url: '/backend/page/member/add.html',name: '添加员工'},true)} else {window.parent.menuHandle({id: '2',url: '/backend/page/member/add.html?id='+st,name: '修改员工'},true)}
}
</script>

第二步: 在add.html页面中获取URL中携带的员工id,如果获取得到id发起修改员工的Ajax请求,如果获取不到发起添加员工的Ajax请求

created() {this.id = requestUrlParam('id')// id有值表示修改,没有值表示添加this.actionType = this.id ? 'edit' : 'add'if (this.id) {this.init()}
}// 获取url地址上面的参数
function requestUrlParam(argname){var url = location.hrefvar arrStr = url.substring(url.indexOf("?")+1).split("&")for(var i =0;i<arrStr.length;i++){// 查找"id="出现的索引var loc = arrStr[i].indexOf(argname+"=")if(loc!=-1){return arrStr[i].replace(argname+"=","").replace("?","")}}return ""
}

员工信息回显

第一步: 根据编辑员工的id查询员工信息然后以json形式响应给页面

@GetMapping("/{id}")// 使用占位符接收员工的id
public Result<Employee> getById(@PathVariable Long id){log.info("根据id查询员工信息..");Employee employee = employeeService.getById(id);return Result.success(employee);
}

第二步: 在回调函数中接收服务端响应的json数据,并通过Vue的双向绑定功能进行员工信息回显

  • 前端接收到服务端响应的json数据之后先判断状态码是否为1,如果是1则说明操作成功,然后将获取到的数据渲染到表单中从而达到回显数据的效果
// created钩子函数中调用的init函数
async init () {queryEmployeeById(this.id).then(res => {console.log(res)if (String(res.code) === '1') {console.log(res.data)this.ruleForm = res.datathis.ruleForm.sex = res.data.sex === '0' ? '女' : '男'// this.ruleForm.password = ''} else {this.$message.error(res.msg || '操作失败')}})
}

更新员工信息

第一步: 用户点击保存按钮发送Ajax请求将页面中修改后的员工信息以json形式提交给服务端

<el-buttontype="primary"@click="submitForm('ruleForm', false)"
>保存
</el-button>
<script>submitForm (formName, st) {this.$refs[formName].validate((valid) => {if (valid) {// 判断是添加还是修改 if (this.actionType === 'add') {// 添加员工信息的接口addEmployee(params).then(res => {// 接收服务器响应的结果执行回调函数} else {                        const params = {...this.ruleForm,sex: this.ruleForm.sex === '女' ? '0' : '1'}// 修改员工信息editEmployee(params).then(res => {if (res.code === 1) {this.$message.success('员工信息修改成功!')this.goBack()} else {this.$message.error(res.msg || '操作失败')}}).catch(err => {this.$message.error('请求出错了:' + err)})}} else {console.log('error submit!!')return false}})
}
// 修改员工的接口
function editEmployee (params) {return $axios({url: '/employee',method: 'put',data: { ...params }})
}
</script>

第二步: 服务端将修改后的员工信息保存到数据库,然后响应成功的结果状态信息

@PutMapping
public Result<String> update(@RequestBody Employee employee, HttpServletRequest request) {log.info(employee.toString());Long id = (Long) request.getSession().getAttribute("employee");employee.setUpdateUser(id);employee.setUpdateTime(LocalDateTime.now());employeeService.updateById(employee);return Result.success("员工信息修改成功");
}

第三步: 页面Ajax请求的回调函数接收到服务端响应的信息后进行相应处理,调用goBack函数跳转至员工管理页面

goBack(){window.parent.menuHandle({id: '2',url: '/backend/page/member/list.html',name: '员工管理'},false)
}

填充公共字段

添加/修改员工数据的时候,都需要指定一下创建人,创建时间,修改人,修改时间等公共字段,在员工表,菜品表,分类表等其他表中都拥有的字段

使用MybatisPlus提供的自动填充功能将这些公共字段在一个地方统一管理,在插入或者更新的时候为指定字段赋予指定的值

实现步骤

第一步: 在实体类的需要自动填充的属性上方加入@TableFiled注解并指定自动填充的策略

@Data
public class Employee implements Serializable {private static final long serialVersionUID = 1L;private Long id;private String username;private String name;private String password;private String phone;private String sex;private String idNumber;//身份证号码private Integer status;// 插入时填充字段@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;// 插入和更新时填充字段@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;@TableField(fill = FieldFill.INSERT)private Long createUser;@TableField(fill = FieldFill.INSERT_UPDATE)private Long updateUser;
}

第二步: 按照框架要求编写元数据对象处理器common/MyMetaObjectHandle在此类中实现插入和更新的方法并统一对公共字段赋值

  • 在MyMetaObjectHandler类中不能获得HttpSession对象,所以我们需要使用ThreadLocal存储当前登录用户的Id
方法名功能
metaObject(元数据)封装了需要执行更新和插入的实体类对象,它提供了setValue方法用来完成字段的填充
insertFill(MetaObject metaObject)在执行插入语句时对指定的公共字段进行填充
updateFill(MetaObject metaObject)在执行更新语句时对指定的公共字段进行填充
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {log.info("公共字段自动填充(insert)...");log.info(metaObject.toString());metaObject.setValue("createTime", LocalDateTime.now());metaObject.setValue("updateTime", LocalDateTime.now());}@Overridepublic void updateFill(MetaObject metaObject) {log.info("公共字段自动填充(update)...");log.info(metaObject.toString());metaObject.setValue("updateTime", LocalDateTime.now());}
}

ThreadLocal

对于客户端每次发送的Http请求, 服务端都会创建一个对应的线程来处理

  • 在一次请求过程中LocalCheekFilter中的doFilter-->EmployeeController中的update-->MyMetaObjectHandler中的updateFill处于同一个线程当中

ThreadLocal并不是一个Thread(线程)而是Thread的局部变量,主要为每个使用该变量的线程提供独立的变量副本

  • 使用ThreadLocal维护变量时每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
  • ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问
方法名功能
public void set(T value)设置当前线程对应的线程局部变量的值
public T get()返回当前线程所对应的线程局部变量的值

第一步: 新建一个基于ThreadLocal的工具类common/BaseContext ,用于存储和获取当前登录用户的Id

public class BaseContext {private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();public static void setCurrentId(Long id) {threadLocal.set(id);}public static Long getCurrentId() {return threadLocal.get();}
}

第二步: 在LoginCheckFilter类中使用request.getSession获取当前登录用户的Id,然后使用BaseContext工具类将Id值存到ThreadLocal当中,key是当前线程

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 判断用户的登录状态,如果已登录,则直接放行if (request.getSession().getAttribute("employee") != null) {log.info("用户已登录,id为{}", request.getSession().getAttribute("employee"));// 在这里获取一下当前线程的idlong id = Thread.currentThread().getId();log.info("doFilter的线程id为:{}", id);// 从session中获取之前我们存的用户IdLong empId = (Long) request.getSession().getAttribute("employee");// 使用BaseContext工具类将用户的Id存到ThreadLocal当中,key是当前线程BaseContext.setCurrentId(empId);filterChain.doFilter(request, response);return;}
}

第三步: 在MyMetaObjectHandler类中使用BaseContext工具类从当前线程中获取对应登录用户的Id,然后填充到对应表中的字段

@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {// 插入操作自动填充@Overridepublic void insertFill(MetaObject metaObject) {log.info("公共字段填充(create)...");metaObject.setValue("createTime", LocalDateTime.now());metaObject.setValue("updateTime", LocalDateTime.now());// 获取当前线程对应的用户Id,然后设置创建人IdmetaObject.setValue("createUser", BaseContext.getCurrentId());metaObject.setValue("updateUser", BaseContext.getCurrentId());}// 更新操作自动填充@Overridepublic void updateFill(MetaObject metaObject) {log.info("公共字段填充(insert)...");metaObject.setValue("updateTime", LocalDateTime.now());// 设置更新人idmetaObject.setValue("updateUser", BaseContext.getCurrentId());}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/190374.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2022年06月 Python(五级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 Python中 print(“八进制{: o}”.format(12)) 正确的输出结果是?( ) A: 八进制:O B: 八进制:O14 C: 八进制14O D: 八进制14 答案:D 字符串的format()格式。 第2题 下列的程…

java的类和继承构造

一些小技巧 类和对象 什么是类&#xff0c;对象&#xff0c;方法&#xff1f; 在下面的 Java 代码中&#xff0c;定义了一个名为 Person 的类&#xff0c;并提供了构造方法来初始化对象的属性。类中定义了 eat、sleep 和 work 三个方法&#xff0c;用于表示人的行为。在 main 方…

LINUX入门篇【4】开发篇--开发工具vim的使用

前言&#xff1a; 从这一篇开始&#xff0c;我们将正式进入使用LINUX进行写程序和开发的阶段&#xff0c;可以说&#xff0c;由此开始&#xff0c;我们才开始真正去使用LINUX。 介绍工具&#xff1a; 1.LINUX软件包管理器yum&#xff1a; 1.yum的介绍&#xff1a; 在LINUX…

【FPGA】十进制计数器 | 实现 4-bit 2421 十进制计数器 | 有限状态机(FSM)

目录 Ⅰ. 实践说明 0x00 十进制计数器 0x01 有限状态机&#xff08;FSM&#xff09; Ⅱ. 实践部分 0x00 4-bit 2421 十进制计数器 Ⅰ. 实践说明 0x00 十进制计数器 十进制计数器是一种以十进制运算的计数器&#xff0c;从 0 数到 9&#xff0c;然后返回 0 状态。由于它需…

2023NewStarCTF

目录 一、阳光开朗大男孩 二、大怨种 三、2-分析 四、键盘侠 五、滴滴滴 六、Include? 七、medium_sql 八、POP Gadget 九、OtenkiGirl 一、阳光开朗大男孩 1.题目给出了secret.txt和flag.txt两个文件&#xff0c;secret.txt内容如下&#xff1a; 法治自由公正爱国…

玩转硬件之Micro:bit的玩法(五)——垃圾分类

垃圾分类&#xff0c;为了美好的明天 垃圾是我们生活中不可避免的产物&#xff0c;每天都有大量的垃圾被丢弃&#xff0c;如果不加以处理&#xff0c;就会给环境和人类带来严重的危害。 垃圾分类是一种有效的垃圾管理方式&#xff0c;它是指按照一定的标准或规则&#xff0c;将…

计算机组成原理之概述

概述 计组主要讲的是计算机的硬件实现方式。 机器字长 比如8080处理器&#xff0c;如果想处理16位数的整数运算&#xff0c;就需要执行两次。 可见&#xff0c;机器字长会影响到数据的处理速度。 计算机硬件的基本组成 早期的冯诺依曼机 冯诺依曼提出了“存储程序”的概念&…

Socket网络编程

本文主要讲解Socket网络编程。 首先介绍socket&#xff0c;包括TCP和UDP通信过程&#xff1b;然后介绍常用的函数&#xff1b;最后编写client-server例子&#xff0c;并进行测试。 文章目录 Socket介绍TCP通信过程服务器端通信过程&#xff1a;客户端通信过程&#xff1a; UDP通…

智慧工地源码:助力数字建造、智慧建造、安全建造、绿色建造

智慧工地围绕建设过程管理&#xff0c;建设项目与智能生产、科学管理建设项目信息生态系统集成在一起&#xff0c;该数据在虚拟现实环境中&#xff0c;将物联网收集的工程信息用于数据挖掘和分析&#xff0c;提供过程趋势预测和专家计划&#xff0c;实现工程建设的智能化管理&a…

Golang源码分析 | 程序引导过程

环境说明 CentOS Linux release 7.2 (Final&#xff09; go version go1.16.3 linux/amd64 GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7使用gdb查看程序入口 编写一个简单的go程序 // main.go package mainfunc main() {print("Hello world") } 编译go …

假冒 Skype 应用程序网络钓鱼分析

参考链接: https://slowmist.medium.com/fake-skype-app-phishing-analysis-35c1dc8bc515 背景 在Web3世界中&#xff0c;涉及假冒应用程序的网络钓鱼事件相当频繁。慢雾安全团队此前曾发表过分析此类网络钓鱼案例的文章。由于Google Play在中国无法访问&#xff0c;许多用户…

K8S知识点(十)

&#xff08;1&#xff09;Pod详解-启动命令 创建Pod&#xff0c;里面的两个容器都正常运行 &#xff08;2&#xff09;Pod详解-环境变量 &#xff08;3&#xff09;Pod详解-端口设置 &#xff08;4&#xff09;Pod详解-资源配额 修改&#xff1a;memory 不满足条件是不能正常…

Django之三板斧的使用,全局配置文件介绍,request对象方法,pycharm链接数据库,Django链接数据库,ORM的增删改查

【1】三板斧(3个方法)的使用 Httpresponse() 括号内写什么字符串&#xff0c;返回的就是什么字符串返回的是字符串 render(request&#xff0c; 静态文件 ) request是固定的静态文件是写在templates文件夹里面的&#xff0c;如&#xff0c;HTML文件 redirect( 重定向的地址 ) 重…

Hadoop原理,HDFS架构,MapReduce原理

Hadoop原理&#xff0c;HDFS架构&#xff0c;MapReduce原理 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c…

LeetCode刷题总结(一)

文章目录 前言题型排序问题动态规划 前言 本文把刷题过程中的总结记下来&#xff0c;方便未来回顾的时候继续拓展。 题型 排序问题 排序问题的解决方法有很多。对于简单算法来说&#xff0c;最重要的是记住思路&#xff1b;对于高级算法来说&#xff0c;最重要的是记住细节…

利用LangChain实现RAG

检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;结合了搜寻检索生成能力和自然语言处理架构&#xff0c;透过这个架构&#xff0c;模型可以从外部知识库搜寻相关信息&#xff0c;然后使用这些信息来生成response。要完成检索增强生成主要包含四个步骤…

2023亚太杯数学建模A题思路

文章目录 0 赛题思路1 竞赛信息2 竞赛时间3 建模常见问题类型3.1 分类问题3.2 优化问题3.3 预测问题3.4 评价问题 4 建模资料5 最后 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 竞赛信息 2023年第十三…

【中间件篇-Redis缓存数据库08】Redis设计、实现、redisobject对象设计、多线程、缓存淘汰算法

Redis的设计、实现 数据结构和内部编码 type命令实际返回的就是当前键的数据结构类型&#xff0c;它们分别是&#xff1a;string(字符串)hash(哈希)、list(列表)、set(集合)、zset (有序集合)&#xff0c;但这些只是Redis对外的数据结构。 实际上每种数据结构都有自己底层的…

【DP】背包问题全解

一.简介 DP&#xff08;动态规划&#xff09;背包问题是一个经典的组合优化问题&#xff0c;通常用来解决资源分配的问题&#xff0c;如货物装载、投资组合优化等。问题的核心思想是在有限的资源约束下&#xff0c;选择一组物品以最大化某种价值指标&#xff0c;通常是总价值或…

【Java 进阶篇】Java与JQuery选择器:解锁前端开发的魔法大门

在前端开发的世界中&#xff0c;选择器是我们与HTML文档进行互动的钥匙&#xff0c;而Java和JQuery则为我们提供了强大的工具&#xff0c;使得前端开发不再是一个艰深的谜题。本篇博客将围绕Java与JQuery选择器展开&#xff0c;深入解析选择器的奥秘&#xff0c;为你打开前端开…