mes系统(更新中)

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、

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦