mes系统从入门到精(fang)通(qi)
mes简介
MES(Manufacturing Execution System)即制造企业生产过程执行系统,是一套面向制造企业车间执行层的生产信息化管理系统。MES 可以为企业提供包括制造数据管理、计划排产管理、生产调度管理、库存管理、质量管理、人力资源管理、工作中心/设备管理、工具工装管理、采购管理、成本管理、项目看板管理、生产过程控制、底层数据集成分析、上层数据集成分解等管理模块,为企业打造一个扎实、可靠、全面、可行的制造协同管理平台。
背景:机加工厂额系统流程管理系统
目的:严格把控产品从原料到成平的所有过程并且完成余料的计算
特点:价格低-兼职外包、时间少-2个月 熟悉企业开发的模式
学习的心路历程
案例流程分析
大的流程
订单产生需求,计划部门核实订单内容,采购部采购材料,入库管理(原料库,半成品库,成平库,废料库),车间生产(派工+质检),记录过程,系统管理,数据分析(echarts)
细节流程
订单部门:(钢锭,外协件,外购件,半成品)(隐含在背后的有:废料( 其他子件的原料))
–接到客源任务订单,要求生产xxx产品,由公司分析组成的各个部件,对每个部件生成一个生产订单。
钢锭:生产半成品的原料
半成品:外协件,外购件,钢锭生成而来的半成品
钢锭和半成品的关系
钢锭 1 对 * 一个钢锭可以生成多个不同类的半成品,也有可能是成品
半成品 1 对 1 一个半成品生成一个成品
总结性描述:订单生成需要锻造出来的钢锭件,然后采购部门采购钢锭原料,锻造生成订单贵哥的钢锭件。钢锭用于生成半成品,当然也有可能生产为成品,半成品另外还有外协件,外购件,在加工成成品的过程中,产生的废料
归入库中管理。
订单部门的功能要求: 1、主管有增删改的权利,一般员工查看订单
2、订单有两种状态,启动和未启动
3、订单一旦生成,计划部门讲生成未启动计划
4、对分页有要求,可以模糊查询+时间段,dml语句操作,完成后的重定向
计划部门:订单确定而来
锻造生产计划,办成品(外协件,外购件,办成平,废料)生产计划,钢锭原料生产计划)
订单部门的功能需求: 1、订单一旦启动,计划对应生成——我们实现一个订单对应一个计划的功能,实际上还有钢锭原料锻造成钢锭半成品的计划
2、待条件(查询条件跟订单不同)地分页显示,(已启动计划,未启动计划)
3、未启动计划可以批量安排启动和结束日期,编程已启动计划,但是对于已启动订单又可以让其停止启动状态
4、可以对其中的某些字段内容进行改变,特别注意交货状态,一旦更改,对应订单的交货状态一起发生改变(是逻辑的更改,不要做成表的更改)
5、简化结构——分离模板(减少每一页代码的冗余)
技术攻关
准备环境
ssm环境搭建
Spring环境配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:property-placeholder location="classpath*:db/dbproperties.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置一个可以执行批量的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory"
ref="sqlSessionFactory"></constructor-arg>
<constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:db/mybatis-config.xml" />
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations">
<list>
<value>classpath*:mapper/*Mapper.xml</value>
</list>
</property>
</bean>
<!-- 管理mybatismapper.xml文件 -->
<bean id="mapperScannerConfigurer"
class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.hxy.**.mapper" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:advice id="transactionAdvice"
transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="edit*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="new*" propagation="REQUIRED" />
<tx:method name="set*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="change*" propagation="REQUIRED" />
<tx:method name="get*" propagation="REQUIRED"
read-only="true" />
<tx:method name="find*" propagation="REQUIRED"
read-only="true" />
<tx:method name="select*" propagation="REQUIRED"
read-only="true" />
<tx:method name="load*" propagation="REQUIRED"
read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceMethod" expression="execution(* com.hxy.service.*.*(..))"/>
<aop:advisor advice-ref="transactionAdvice" pointcut-ref="serviceMethod"/>
</aop:config>
</beans>
Springmvc环境配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:annotation-config/>
<!-- 启动包扫描功能 -->
<context:component-scan base-package="com.hxy.controller" />
<context:component-scan base-package="com.hxy.service" />
<!-- 启动注解驱动的spring mvc 功能 -->
<mvc:annotation-driven/>
<mvc:resources location="/js/" mapping="/js/**"/>
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/bootstrap3.3.5/" mapping="/bootstrap3.3.5/**"/>
<mvc:resources location="/assets/" mapping="/assets/**"/>
<mvc:resources location="/ztree/" mapping="/ztree/**"/>
<mvc:resources location="/WEB-INF/views/order/" mapping="/order/**"/>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
<bean id="jsonView" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Spring beans 配置文件所在目录 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>
<!-- spring mvc 配置 -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- Encoding -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
代码层面
java代码Common包
JsonData:用于传递页面与后代之间的信息,封装了成功信息,数据内容,信息描述
//内省
@Getter
@Setter
public class JsonData{
private boolean ret;//success
private String msg;//info message
private Object data;//返回数据 result
//三个返回成功的构造函数重载
public JsonData(boolean ret){
this.ret = ret;
}
public static JsonData success(Object object,String msg){
JsonData jsonData = new JsonData(true);
jsonData.data = object;
jsonData.msg = msg;
return jsonData;
}
public static JsonData success(Object object){
JsonData jsonData = new JsonData(true);
jsonData.data = object;
return jsonData;
}
public static JsonData success(){
return new JsonData(true);
}
//如果是返回结果出现问题,一定要给这个问题带一个说明
public static JsonData fail(String msg){
JsonData jsonData = new JsonData(false);
JsonData.msg = mgs;
return jsonData;
}
//方便做json的转换的map构造
public Map<String,Object> toMap(){
HashMap<String,Object> result = new HashMap<String,Object>();
result.put("ret",ret);
result.put("msg",msg);
result.put("data",data);
return result;
}
}
ApplicationContext
Spring的上下文,方便程序中获取任意被管理的对象
package com.hxy.common;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/*
* 配置spring上下文
* 拿到bean
*/
@Component("applicationContextHelper")
public class ApplicationContextHelper {
private static ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext context) {
applicationContext = context;
}
public static <T> T popBean(Class<T> clazz) {
if(applicationContext == null) {
return null;
}
return applicationContext.getBean(clazz);
}
public static <T> T popBean(String name,Class<T> clazz) {
if(applicationContext == null) {
return null;
}
return applicationContext.getBean(name, clazz);
}
}
Exception
全局异常处理ExceptionResolver,方便处理在controller中出现的任意异常信息
package com.hxy.common;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import com.hxy.exception.SysMineException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SpringExceptionResovler implements HandlerExceptionResolver{
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
String url = request.getRequestURI().toString();
ModelAndView mv;
String defaultMsg = "System error";
//这里我们要求项目中所有请求json数据,都使用.json结尾
if(url.endsWith(".json")) {
if(ex instanceof SysMineException) {
JsonData result = JsonData.fail(ex.getMessage());
mv = new ModelAndView("jsonView",result.toMap());
}else {
log.error("unknown json exception,url:"+url,ex);
JsonData result = JsonData.fail(defaultMsg);
mv = new ModelAndView("jsonView",result.toMap());
}
}else if(url.endsWith(".page")) {//这里我们要求项目中所有请求page数据,都使用.page结尾
log.error("unknown page exception,url"+url,ex);
JsonData result = JsonData.fail(defaultMsg);
mv = new ModelAndView("exception",result.toMap());
}else {
log.error("unknown exception,url"+url,ex);
JsonData result = JsonData.fail(defaultMsg);
mv = new ModelAndView("jsonView",result.toMap());
}
return mv;
}
}
HttpInterceptor
用于测试每个请求到执行的代码效率
package com.hxy.common;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.mvc.condition.RequestConditionHolder;
import com.hxy.util.JsonMapper;
import lombok.extern.slf4j.Slf4j;
/*
* 一般情况下,对来自浏览器的请求的拦截,是利用Filter实现的
而在Spring中,基于Filter这种方式可以实现Bean预处理、后处理
而Spring MVC也有拦截器,不仅可实现Filter的所有功能,还可以更精确的控制拦截精度。
Spring MVC提供的org.springframework.web.servlet.handler.HandlerInterceptorAdapter这个适配器,继承此类,可以非常方便的实现自己的拦截器。
*/
@Slf4j
public class HttpInterceptor extends HandlerInterceptorAdapter {
private static final String START_TIME = "requestStartTime";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String url = request.getRequestURI().toString();
Map parameterMap = request.getParameterMap();
log.info("request start. url:{}, params:{}",url,JsonMapper.obj2String(parameterMap));
long start = System.currentTimeMillis();
request.setAttribute(START_TIME, start);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
String url = request.getRequestURI().toString();
long start = (long) request.getAttribute(START_TIME);
long end = System.currentTimeMillis();
log.info("request finished. url :{},cost:{}",url,end - start);
removeThreadLocalInfo();
}
private void removeThreadLocalInfo() {
RequestHolder.remove();
}
}
Utils
beanvalidator
前段页面传递过来的vo对象,在service层需要对其做数据校验(检查是否为空值,检查传递过来的成员变量中的值是否符合要求的等等),采用技术是beanvalidator
package com.hxy.util;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.apache.commons.collections.MapUtils;
import org.apache.ibatis.builder.ParameterExpression;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hxy.exception.ParamException;
public class BeanValidator {
//javax.validation.Validation java的校验规范
//有java自带的校验工厂生成指定的校验器---hibernate-validator
private static ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
public static <T> Map<String,String> validate(T t,Class... groups){
Validator validator = validatorFactory.getValidator();
Set validateResult = validator.validate(t, groups);
if(validateResult.isEmpty()) {
return Collections.emptyMap();
} else {
LinkedHashMap errors = Maps.newLinkedHashMap();
Iterator iterator = validateResult.iterator();
while (iterator.hasNext()) {
ConstraintViolation violation = (ConstraintViolation) iterator.next();
errors.put(violation.getPropertyPath().toString(), violation.getMessage());
}
return errors;
}
}
public static Map<String,String> validateList(Collection<?> collection){
Preconditions.checkNotNull(collection);
Iterator iterator = collection.iterator();
Map errors;
do {
if(!iterator.hasNext()) {
return Collections.emptyMap();
}
Object object = iterator.next();
errors = validate(object, new Class[0]);
}while(errors.isEmpty());
return errors;
}
public static Map<String,String> validateObject(Object first,Object...objects){
if(objects !=null && objects.length>0) {
return validateList(Lists.asList(first, objects));
} else {
return validate(first,new Class[0]);
}
}
public static void check(Object param) throws ParamException{
Map<String,String> map = BeanValidator.validateObject(param);
if(MapUtils.isNotEmpty(map)) {
throw new ParamException(map.toString());
}
}
}
MyStringUtils
从页面传回来的vo里面的数据都是String类型,而存储至数据库的Po里面的时间Date类型(举例‘),所以需要自己定义一个来转换页面返回的数据
package com.hxy.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
final public class MyStringUtils {
// 默认处理日期
private static String defaultDateFormat = "yyyy-MM-dd";
// 日强转字符串
public static String date2String(Object... objects) {
Date date = null;
String format = null;
if (objects != null && objects.length > 0) {
if (objects[0] != null && objects[0] instanceof Date) {
date = (Date) objects[0];
}
if (objects[1] != null && objects[1] instanceof String) {
format = (String) objects[1];
} else {
format = defaultDateFormat;
}
}
if (date != null) {
SimpleDateFormat sdf = new SimpleDateFormat(format);
return sdf.format(date);
}
throw new RuntimeException("日期格式有问题,请检查输入参数");
}
public static Date string2Date(String date, String format) {
try {
Date dateTemp = null;
if (date != null && date.length() > 0) {
if (format == null)
format = defaultDateFormat;
SimpleDateFormat sdf = new SimpleDateFormat(format);
dateTemp = sdf.parse(date);
}
return dateTemp;
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
mustache模板分页
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<script id="paginateTemplate" type="x-tmpl-mustache">
<div class="col-xs-6">
<div class="dataTables_info" id="dynamic-table_info" role="status" aria-live="polite">
总共 条中的 ~ 条 当前第页
</div>
</div>
<div class="col-xs-6">
<div class="dataTables_paginate paging_simple_numbers" id="dynamic-table_paginate">
<ul class="pagination">
<li class="paginate_button previous disabled" aria-controls="dynamic-table" tabindex="0">
<a href="#" data-target="1" data-url="" class="page-action">首页</a>
</li>
<li class="paginate_button disabled" aria-controls="dynamic-table" tabindex="0">
<a href="#" name="前一页" data-target="" data-url="" class="page-action">前一页</a>
</li>
<li class="paginate_button active" aria-controls="dynamic-table" tabindex="0">
<a href="#" data-id="" >第页</a>
<input type="hidden" class="pageNo" value="" />
</li>
<li class="paginate_button disabled" aria-controls="dynamic-table" tabindex="0">
<a href="#" name="后一页" data-target="" data-url="" class="page-action">后一页</a>
</li>
<li class="paginate_button next disabled" aria-controls="dynamic-table" tabindex="0">
<a href="#" data-target="" data-url="" class="page-action">尾页</a>
</li>
</ul>
</div>
</div>
</script>
<!-- 不要求会写,但是希望你掌握-->
<script type="text/javascript">
//01找到模板中的内容,把html中的数据封装到js自定义的变量中
//paginationTemplate 分页模板的英文,自定义名字
var paginateTemplate = $("#paginateTemplate").html();
//02将页面变量内容交给mustache库,使用mustache技术加载指定模板
Mustache.parse(paginateTemplate);
//渲染分页的内容
//url total pageNo pageSize currentSize idElement:将paginatetemplate加载到哪一个页面版块 预留一个回调函数
function renderPage(url, total, pageNo, pageSize, currentSize, idElement,
callback) {
//得到最大的页码,使用向上取整的函数
var maxPageNo = Math.ceil(total / pageSize);
//url: /order/page.json?username=apple&age=100
var paramStartChar = url.indexOf("?") > 0 ? "&" : "?";
//数据从哪里开始
var from = (pageNo - 1) * pageSize + 1;
//处理当前页为数字类型
var pageNo = parseInt(pageNo);
//定义视图内容[1][2]当前页[前一页][后一页]...
var view = {
from : from > total ? total : from,
to : (from + currentSize - 1) > total ? total
: (from + currentSize - 1),
total : total,
pageNo : pageNo,
maxPageNo : maxPageNo,
nextPageNo : pageNo >= maxPageNo ? maxPageNo : (pageNo + 1),
beforePageNo : pageNo == 1 ? 1 : (pageNo - 1),
firstUrl : (pageNo == 1) ? '' : (url + paramStartChar
+ "pageNo=1&pageSize=" + pageSize),
beforeUrl : (pageNo == 1) ? '' : (url + paramStartChar + "pageNo="
+ (pageNo - 1) + "&pageSize=" + pageSize),
nextUrl : (pageNo >= maxPageNo) ? '' : (url + paramStartChar
+ "pageNo=" + (pageNo + 1) + "&pageSize=" + pageSize),
lastUrl : (pageNo >= maxPageNo) ? '' : (url + paramStartChar
+ "pageNo=" + maxPageNo + "&pageSize=" + pageSize)
};
//idElement="orderPage"
//找到页面中的<div class="row" id="orderPage"></div>
//Mustache.render(paginateTemplate, view) 使用mustache模板,对指定模板进行数据填充
$("#" + idElement).html(Mustache.render(paginateTemplate, view));
//每一个页码按钮拥有一个点击事件
$(".page-action").click(function(e) {
//阻止默认事件,冒泡
e.preventDefault();
//给每一个按钮添加指定的值
$("#" + idElement + " .pageNo").val($(this).attr("data-target"));
//填充每一个按钮上的url
var targetUrl = $(this).attr("data-url");
//让回调函数执行这个url
if (targetUrl != '') {
callback(targetUrl);//交由其他地方的ajax请求来传输页码,url以及数据
}
})
}
</script>
开发问题记录
1、日期显示模块:
日期显示插件js代码:
//日期显示
$('.datepicker').datepicker({
dateFormat:'yy-mm-dd',
showOtherMonths:true,
selectOtherMonths:false
})
这里有一个很鸡肋的肌肉记忆,在我们学习java.util类的SimpleDataFormat的类,里面有一个我们管用到的语法操作
// 默认处理日期
private static String defaultDateFormat = "yyyy-MM-dd";
SimpleDateFormat sdf = new SimpleDateFormat(format);
这里我们经常会写到yyyy-MM-dd这个日期的格式但是注意到这里,从页面传回来的vo到存进数据库的po的格式是不一样的,而在网页上传输的时候,我们要用yy-mm-dd的格式,不然在MyStringUtils时会出现格式转换错误。
2、