您的位置:首页 > 数据库 > Oracle

随机算法解决生产调度问题 C# Oracle

2020-06-06 07:42 615 查看

解决这样一个问题,设想机器有500台(注塑机),工单有3000张,每张工单只能分配到特定范围的机器(按照注塑机吨位分配),并且每张工单可以用日产能算出需要多少天,如何一次分配,既使每张工单都能分到机器,又让每台机器都分到工单,并且尽量保证注塑机效率最高(效率最高的意思是开机率最高)。
另外,注塑机出产品可以一个模具出不同的产品,所以很多时候需要考虑给一张工单搭配一个生产数量差不多的联产品,以保证效率。

using Oracle.ManagedDataAccess.Client;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace GAInJobShop
{
public partial class Form1 : Form
{

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
MachineQuery();
}
private void MachineQuery()
{
string sql;
sql = @"SELECT eci01 machine,machine_ton,machine_down FROM injection_plan_machine ORDER BY eci01";
DataTable dt= OracleHelper.ExecuteDataTable(sql);
dt.Columns.Add("Select",typeof(bool));
dt.Columns["Select"].SetOrdinal(0);
dataGridView1.DataSource = dt;
//dt.Columns["Select"].DefaultValue = false;

for (int i = 0; i < dataGridView1.RowCount; i++)
{
dataGridView1.Rows[i].Cells["Select"].Value = false;
if (dataGridView1.Rows[i].Cells["machine_down"].Value.ToString() == "Y")
{
dataGridView1.Rows[i].Cells["Select"].Value = true;
}
}
}
private void GA()
{
string sql;
//更新需要排的工單
sql = "TRUNCATE TABLE injection_plan";
OracleHelper.ExecuteNonQuery(sql);
sql = @"insert into injection_plan SELECT CAST(0 AS INT) Ver
,SFB01 job_num,
SFB102 machine,
SFB13 start_date,
SFB15 end_date,
SFB05 partnum,
SFB08 prod_qty,
SFB09 complete_qty,
sfb08-sfb09 uncomplete_qty,
A1 daily_plan_qty,
CEIL((sfb08-sfb09)/A1) need_qty,
get_forward_date(CEIL((sfb08-sfb09)/A1)) estimate_complete_date,
TA_IMA001 suggestion_workshop,
TC_MMJ09 ton,
gong_mu,
tc_mmi01 mold
FROM SFB_FILE
LEFT JOIN IMA_FILE
ON IMA01 = SFB05
LEFT JOIN OEB_FILE
ON OEB01 = SFB22
AND OEB03 = SFB221
LEFT JOIN mu_ju_zui_da_chan_neng2
ON sfb05=tc_mmj03
WHERE SFB04 <> '8'
AND SFB01 LIKE '%511-%'
AND SFB02 NOT IN ('7', '8')
AND SFB08 <> SFB09
AND sfb13>get_forward_date(-45)
AND A1 IS NOT NULL
ORDER BY sfb102,sfb13";
OracleHelper.ExecuteNonQuery(sql);
//MessageBox.Show("已經更新工單!!!!");

//產生第二代,為了把沒有機台的隨機排到機台
sql = @"insert into injection_plan SELECT 1 Ver,
job_num,
machine,
start_date,
end_date,
partnum,
prod_qty,
complete_qty,
uncomplete_qty,
daily_plan_qty,
need_days,
estimate_complete_date,
suggestion_workshop,
ton,
gong_mu,
mold
FROM injection_plan
where Ver=0 and need_days>" + NeedDays.Text + " ";
OracleHelper.ExecuteNonQuery(sql);
//MessageBox.Show("產生第二代!!!!");
//sql = @"SELECT job_num,ton,gong_mu,mold FROM injection_plan WHERE ver=1 ORDER BY machine";
//DataTable NotScheduleJobNumDt = OracleHelper.ExecuteDataTable(sql);
DataTable NotScheduleJobNumDt = new DataTable();
DataTable NotScheduleMachineDt = new DataTable();
Random rd = new Random(int.Parse(DateTime.Now.ToString("ss")) + 20);
Random rd2 = new Random(int.Parse(DateTime.Now.ToString("ss")));
Random rd3 = new Random(int.Parse(DateTime.Now.ToString("ss")) + 30);
Random rd4 = new Random(int.Parse(DateTime.Now.ToString("ss")) + 40);
int randkey, randkey2, randkey3, randkey4;

OracleConnection conn = new OracleConnection(OracleHelper.connStr);
conn.Open();
OracleCommand cmd = conn.CreateCommand();
OracleDataAdapter adapter = new OracleDataAdapter(cmd);

//MessageBox.Show("二代已隨機更新生產線!!!!");
//產生第二代,為了把沒有機台的隨機排到機台
sql = @"insert into injection_plan SELECT 2 Ver,
job_num,
machine,
start_date,
end_date,
partnum,
prod_qty,
complete_qty,
uncomplete_qty,
daily_plan_qty,
need_days,
estimate_complete_date,
suggestion_workshop,
ton,
gong_mu,
mold
FROM injection_plan
where Ver=1 ";
cmd.CommandText = sql;
cmd.ExecuteNonQuery();

//MessageBox.Show("三代已隨機更新生產線!!!!");
//開始變異
/*
sql = @"SELECT ECI01 FROM eci_file WHERE eci03 IN ('INJA','INJB','INJC','INJD','INJE')
AND eci01 LIKE '%J%' AND eciacti='Y' AND eci01<>'JL01'
AND eci03 IN ('INJA','INJB')
AND LENGTH(ECI01)=5
UNION ALL
SELECT ECI01 FROM eci_file WHERE eci03 IN ('INJA','INJB','INJC','INJD','INJE')
AND eci01 LIKE '%J%' AND eciacti='Y' AND eci01<>'JL01'
AND eci03 NOT IN ('INJA','INJB') AND LENGTH(ECI01)=4
order by eci01";
cmd.CommandText = sql;
DataTable datatable4 = new DataTable();
adapter.Fill(datatable4);
NotScheduleMachineDt.Clear();
NotScheduleMachineDt = datatable4;
*/

//fitness
sql = @"TRUNCATE TABLE injection_plan_fitness";
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
sql = @"insert into injection_plan_fitness(ver,dev_days)
SELECT ver,ROUND(STDDEV(need_days),3) dev_days
FROM (
SELECT ver,machine,SUM(need_days) need_days FROM injection_plan WHERE ver>=0
GROUP BY ver,machine) GROUP BY ver";
cmd.CommandText = sql;
cmd.ExecuteNonQuery();

//已發料的工單不再進行重新排機
sql = @"TRUNCATE TABLE CanScheduleJobnum";
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
sql = @"  insert into CanScheduleJobnum SELECT job_num FROM injection_plan WHERE  ver=2
and job_num not in ( SELECT DISTINCT job_num FROM (
SELECT sfa06, injection_plan.* FROM injection_plan
LEFT JOIN sfa_file ON job_num = sfa01
WHERE ver = 2 AND sfa06> 0))";
cmd.CommandText = sql;
cmd.ExecuteNonQuery();

//計算每次變異的工單張數
sql = @"SELECT CEIL(COUNT(*)*(" + P.Text + "/100)) FROM injection_plan WHERE ver=2";
int MutationJobQty = int.Parse(OracleHelper.ExecuteDataTable(sql).Rows[0][0].ToString());

for (int i = 0; i < int.Parse(Generation.Text); i++)
{
label14.BeginInvoke(new Action(() => { label14.Text=i.ToString(); }));
//遺傳使用精英模式
int Mutation = int.Parse(EliteGeneration.Text);
Mutation = int.Parse(EliteGeneration.Text) - i / 10000 * 15;//進化壓力,越到後面壓力越大,利於收斂
if (Mutation < 30)
{
Mutation = 30;
}
sql = @"SELECT ver,dev_days FROM (
select ver,dev_days from injection_plan_fitness order by dev_days
) WHERE ROWNUM<=" + Mutation + "  ";
cmd.CommandText = sql;
DataTable datatable53 = new DataTable();
adapter.Fill(datatable53);
if (datatable53.Rows.Count == Mutation)
{
if (decimal.Parse(datatable53.Rows[0][1].ToString()) == decimal.Parse(datatable53.Rows[Mutation - 1][1].ToString()))
{
//收斂退出條件
break;
}
}
randkey4 = rd4.Next(0, datatable53.Rows.Count);

sql = @"insert into injection_plan SELECT " + (i + 3) + @" Ver,
job_num,
machine,
start_date,
end_date,
partnum,
prod_qty,
complete_qty,
uncomplete_qty,
daily_plan_qty,
need_days,
estimate_complete_date,
suggestion_workshop,
ton,
gong_mu,
mold
FROM injection_plan
where Ver=" + datatable53.Rows[randkey4][0] + " "; //精英模式複製
cmd.CommandText = sql;
cmd.ExecuteNonQuery();

if (i <= 1000)
{
/*
sql = @" SELECT job_num,PartNum,ton,gong_mu,mold FROM injection_plan WHERE
job_num in ( SELECT job_num FROM CanScheduleJobnum) and ver=" + (i + 3) + @"
and machine IS NULL ";
*/
sql = @" SELECT t0.job_num,PartNum,prod_qty,ton,gong_mu,mold FROM injection_plan t0
left join  CanScheduleJobnum t1 on t0.job_num=t1.job_num
where t1.job_num is not null
and ver=" + (i + 3) + @"
and machine IS NULL ";
}
else
{
/*
sql = @" SELECT job_num,PartNum,ton,gong_mu,mold FROM injection_plan WHERE
job_num in ( SELECT job_num FROM CanScheduleJobnum) and ver=" + (i + 3) + @"
and (machine is null or (machine IN (
SELECT machine FROM (
SELECT machine,SUM(need_days) need_days FROM injection_plan
WHERE ver=" + (i + 3) + @" GROUP BY machine
) WHERE need_days>=" + int.Parse(Days.Text) + @" AND machine IS NOT NULL )))";
*/
sql = @"SELECT t0.job_num,t0.PartNum,t0.prod_qty,t0.ton,t0.gong_mu,t0.mold FROM injection_plan t0
LEFT JOIN (
SELECT machine FROM (
SELECT machine,SUM(need_days/gong_mu) need_days FROM injection_plan
WHERE ver=" + (i + 3) + @" GROUP BY machine
) WHERE need_days>=" + int.Parse(Days.Text) + @" AND machine IS NOT NULL
) t1 ON t0.machine=t1.machine
LEFT JOIN CanScheduleJobnum t3 ON t0.job_num=t3.job_num
WHERE t3.job_num IS NOT NULL
AND ver=" + (i + 3) + @"
and (t0.machine is NULL OR t1.machine IS NOT NULL) ";
}
cmd.CommandText = sql;
DataTable datatable51 = new DataTable();
adapter.Fill(datatable51);
NotScheduleJobNumDt.Clear();
NotScheduleJobNumDt = datatable51;
if (NotScheduleJobNumDt.Rows.Count == 0)
{
continue;
}

//對1%的進行變異
for (int j = 0; j < MutationJobQty - 1; j++)
{
randkey = rd.Next(0, NotScheduleJobNumDt.Rows.Count);
//工單只排對應噸位的注塑機
if (NotScheduleJobNumDt.Rows[randkey]["ton"].ToString().Length == 0)
{
NotScheduleJobNumDt.Rows[randkey]["ton"] = 200;
}
if (i <= 1000)
{
sql = @"select * from injection_plan_machine
WHERE  machine_ton>=" + int.Parse(NotScheduleJobNumDt.Rows[randkey]["ton"].ToString()) + @"
and machine_ton<=" + (int.Parse(NotScheduleJobNumDt.Rows[randkey]["ton"].ToString()) + int.Parse(Ton.Text)) + @"
and MACHINE_DOWN='N' ";
cmd.CommandText = sql;
DataTable datatable6 = new DataTable();
adapter.Fill(datatable6);
NotScheduleMachineDt.Clear();
NotScheduleMachineDt = datatable6;
}
else
{
/*
sql = @"select * from injection_plan_machine
WHERE  machine_ton>=" + int.Parse(NotScheduleJobNumDt.Rows[randkey]["ton"].ToString()) + @"
and machine_ton<=" + (int.Parse(NotScheduleJobNumDt.Rows[randkey]["ton"].ToString()) + int.Parse(Ton.Text))+@"
and ECI01 IN (
SELECT machine FROM (
SELECT machine,SUM(need_days) need_days FROM injection_plan
WHERE ver=" + (i + 3) + @" GROUP BY machine
) WHERE need_days<" + int.Parse(Days.Text) + @" AND machine IS NOT NULL)
union all
select * from injection_plan_machine where eci01 not in(select machine from injection_plan where
machine is not null and ver="+(i+3)+") ";
*/

sql = @"select t0.* from injection_plan_machine t0
LEFT JOIN (
SELECT machine FROM (
SELECT machine,SUM(need_days/gong_mu) need_days FROM injection_plan
WHERE ver=" + (i + 3) + @" GROUP BY machine
) WHERE need_days<" + int.Parse(Days.Text) + @" AND machine IS NOT NULL
) t1 ON t0.eci01=t1.machine
WHERE  machine_ton>=" + int.Parse(NotScheduleJobNumDt.Rows[randkey]["ton"].ToString()) + @"
and machine_ton<=" + (int.Parse(NotScheduleJobNumDt.Rows[randkey]["ton"].ToString()) + int.Parse(Ton.Text)) + @"
AND t1.machine IS NOT NULL and t0.MACHINE_DOWN='N'
union all
select t00.* from injection_plan_machine t00
LEFT JOIN (
select machine from injection_plan where
machine is not null and ver=" + (i + 3) + @"
) t01 ON t00.eci01=t01.machine
WHERE  t01.machine IS NULL and t00.MACHINE_DOWN='N' ";
cmd.CommandText = sql;
DataTable datatable6 = new DataTable();
adapter.Fill(datatable6);
NotScheduleMachineDt.Clear();
NotScheduleMachineDt = datatable6;
}
if (NotScheduleMachineDt.Rows.Count == 0)
{
continue;
}

randkey2 = rd2.Next(0, NotScheduleMachineDt.Rows.Count);
sql = @"update injection_plan set machine='" + NotScheduleMachineDt.Rows[randkey2]["ECI01"] + @"'
where job_num='" + NotScheduleJobNumDt.Rows[randkey]["job_num"].ToString() + "' and ver=" + (i + 3) + " ";
cmd.CommandText = sql;
cmd.ExecuteNonQuery();

//現在考慮1個模具出2個膠件的情況,找到剛才這張工單使用的模具,找到應模具使用的料號,
//找到料號對應的工單,更新之
//先處理2個料號共模具 其他的暫時不處理 2020年1月10日10:19:40
if (NotScheduleJobNumDt.Rows[randkey]["gong_mu"].ToString() == "2")
{
//找出共模的料號
sql = @"SELECT tc_mmj03 FROM tc_mmj_file WHERE tc_mmj01='" + NotScheduleJobNumDt.Rows[randkey]["mold"] + @"'
AND tc_mmj03<>'" + NotScheduleJobNumDt.Rows[randkey]["PartNum"] + "' ";
cmd.CommandText = sql;
DataTable datatable211 = new DataTable();
adapter.Fill(datatable211);
//用這個料號去找一個可以排的工單號
//sql = @"SELECT job_num FROM injection_plan
//    WHERE partnum='" + datatable211.Rows[0][0].ToString() + @"'
//    and ver=1 and job_num<>'" + NotScheduleJobNumDt.Rows[randkey]["job_num"].ToString() + @"'
//    and job_num in (select job_num from CanScheduleJobnum) ";//可以排的工單號
/*
sql = @"SELECT job_num FROM injection_plan
WHERE partnum='" + datatable211.Rows[0][0].ToString() + @"'
and ver=1 and job_num<>'" + NotScheduleJobNumDt.Rows[randkey]["job_num"].ToString() + @"'
and exists (select * from CanScheduleJobnum t0,injection_plan t1
where t0.job_num=t1.job_num) AND ROWNUM=1
ORDER BY ABS(prod_qty-" + NotScheduleJobNumDt.Rows[randkey]["prod_qty"] + ") ASC ";//可以排的工單號 //排數量跟他最接近的那個共模工單
*/
sql = @"select job_num from (
SELECT row_number() OVER (ORDER BY ABS(prod_qty-" + NotScheduleJobNumDt.Rows[randkey]["prod_qty"] + @") ASC) seq
,job_num FROM injection_plan
WHERE partnum='" + datatable211.Rows[0][0].ToString() + @"'
and ver=1 and job_num<>'" + NotScheduleJobNumDt.Rows[randkey]["job_num"].ToString() + @"'
and exists (select * from CanScheduleJobnum t0,injection_plan t1
where t0.job_num=t1.job_num) ) where seq=1 ";
cmd.CommandText = sql;
DataTable datatable212 = new DataTable();
adapter.Fill(datatable212);
//找不到共模的工單號,就不處理了,繼續下一次循環
if (datatable212.Rows.Count == 0)
{
continue;
}
//更新這個工單號的機台
randkey3 = 0;
sql = @"update injection_plan set machine='" + NotScheduleMachineDt.Rows[randkey2]["ECI01"] + @"'
where job_num='" + datatable212.Rows[randkey3]["job_num"].ToString() + "' and ver=" + (i + 3) + " ";
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
}
sql = @"insert into injection_plan_fitness(ver,dev_days,max_days,min_days)
SELECT ver,ROUND(STDDEV(need_days),5) dev_days,max(need_days) max_days,min(need_days) min_days
FROM (
SELECT ver,machine,round(SUM(need_days/gong_mu),2) need_days FROM injection_plan WHERE ver=" + (i + 3) + @"
GROUP BY ver,machine) GROUP BY ver";
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
ClearMemory();
}
conn.Close();
label11.BeginInvoke(new Action(() => { label11.Text = "~~~計算完畢~~~"; }));
label11.BeginInvoke(new Action(() => { label18.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); }));
}
private void button1_Click(object sender, EventArgs e)
{
label18.Text = "結束時間";
if (label11.Text == "正在計算中。。。。。。")
{
MessageBox.Show("正在計算中,請不要再次點擊");
return;
}
label10.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
var t1 = Task.Run(() => GA());
label11.Text = "正在計算中。。。。。。";
}
[DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")]
public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize);
/// <summary>
/// 释放内存
/// </summary>
public static void ClearMemory()
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
}
private void button2_Click(object sender, EventArgs e)
{

}

private void button2_Click_1(object sender, EventArgs e)
{

}

private void button2_Click_2(object sender, EventArgs e)
{
string sql;
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
if ((Convert.ToBoolean(dataGridView1.Rows[i].Cells["Select"].Value) == true))
{
string machine = "";
machine = dataGridView1.Rows[i].Cells["machine"].Value.ToString();
sql = @"update injection_plan_machine set MACHINE_DOWN='Y'
where eci01='" + machine + "' ";
OracleHelper.ExecuteNonQuery(sql);
}
else
{
string machine = "";
machine = dataGridView1.Rows[i].Cells["machine"].Value.ToString();
sql = @"update injection_plan_machine set MACHINE_DOWN='N'
where eci01='" + machine + "' ";
OracleHelper.ExecuteNonQuery(sql);
}
}
MachineQuery();
}

private void button3_Click(object sender, EventArgs e)
{
string sql;
sql = @"SELECT * FROM (
SELECT * FROM injection_plan_fitness ORDER BY dev_days)
WHERE ROWNUM<100";
dataGridView2.DataSource = OracleHelper.ExecuteDataTable(sql);
}

private void button4_Click(object sender, EventArgs e)
{
string sql;
sql = @"SELECT ROWNUM,t0.* FROM (
SELECT machine,ROUND(SUM(need_days/gong_mu),0) need_days,COUNT(*) job_qty,MAX(ton) ton
FROM injection_plan
WHERE ver="+GenerationQuery.Text+@"
AND machine NOT IN('FC31','FBC09','JB22','JB02')
GROUP BY machine
ORDER BY NEED_DAYS ASC) t0";
dataGridView3.DataSource = OracleHelper.ExecuteDataTable(sql);
}

private void button5_Click(object sender, EventArgs e)
{
string sql = @"select * from injection_plan where ver="+InjectionG.Text+" ";
dataGridView4.DataSource = OracleHelper.ExecuteDataTable(sql);
}

private void button6_Click(object sender, EventArgs e)
{
if (label18.Text == "結束時間")
{
DateTime startTime = Convert.ToDateTime(label10.Text);
DateTime endTime = DateTime.Now;

TimeSpan ts = endTime - startTime;
//int seconds = (int)ts.TotalSecond;
int sec = (int)ts.TotalSeconds;
label16.Text = (Math.Round(float.Parse(label14.Text) / sec, 2)).ToString("0.00");
}
else
{
DateTime startTime = Convert.ToDateTime(label10.Text);
DateTime endTime = Convert.ToDateTime(label18.Text);

TimeSpan ts = endTime - startTime;
//int seconds = (int)ts.TotalSecond;
int sec = (int)ts.TotalSeconds;
label16.Text = (Math.Round(float.Parse(label14.Text) / sec, 2)).ToString("0.00");
}
}

private void button7_Click(object sender, EventArgs e)
{
string sql = @"select * from injection_plan where ver=" + InjectionG.Text + " ";
ExcelHelper.ToExcel(OracleHelper.ExecuteDataTable(sql));
}

private void button8_Click(object sender, EventArgs e)
{
if ((Convert.ToBoolean(dataGridView1.Rows[0].Cells[0].Value) == false))
{
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
if ((Convert.ToBoolean(dataGridView1.Rows[i].Cells[0].Value) == false))
{
dataGridView1.Rows[i].Cells[0].Value = "True";
}
}
}
else
{
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
if ((Convert.ToBoolean(dataGridView1.Rows[i].Cells[0].Value) == true))
{
dataGridView1.Rows[i].Cells[0].Value = "False";
}
}
}
}

private void button9_Click(object sender, EventArgs e)
{
mychart.ChartAreas.Clear(); //图表区
mychart.Titles.Clear(); //图表标题
mychart.Series.Clear(); //图表序列
mychart.Legends.Clear(); //图表图例

//新建chart图表要素
mychart.ChartAreas.Add(new ChartArea("chartArea"));
mychart.ChartAreas["chartArea"].AxisX.IsMarginVisible = false;
mychart.ChartAreas["chartArea"].Area3DStyle.Enable3D = false;
mychart.Series.Add("均方差回歸");
//mychart.Titles.Add("均方差回歸"); //标题
//mychart.Titles[0].Font = new Font("宋体", 12);
mychart.Series["均方差回歸"].ChartType = SeriesChartType.Point; //图标类型
mychart.Series["均方差回歸"]["PieLineColor"] = "Black";
//mychart.Legends.Add(new Legend("legend"));
mychart.Palette = ChartColorPalette.BrightPastel;
mychart.ChartAreas["chartArea"].AxisY.LabelStyle.IsStaggered = true;
Legend tu_li = new Legend();
mychart.Legends.Add(tu_li);
tu_li.Alignment = StringAlignment.Center;
tu_li.Docking = Docking.Top;
tu_li.Font= new Font("宋体", 24); ;

string sql3 = "SELECT ver,dev_days FROM injection_plan_fitness ORDER BY ver";
DataTable dt2;
dt2 = OracleHelper.ExecuteDataTable(sql3);

mychart.Series["均方差回歸"].Points.DataBind(dt2.AsEnumerable(), "VER", "DEV_DAYS", "");
mychart.Series["均方差回歸"].LabelForeColor = Color.Blue;
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: