基於jms使用ActiveMQ實現異步日誌功能.消息持久到oracle 10g 數據庫

import java.io.Serializable;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
public class BaseEntity implements Serializable {
/**

public String toString() {
return ReflectionToStringBuilder.toString(this);
}

public boolean equals(Object other) {
if ( (this == other ) ) return true;
return EqualsBuilder.reflectionEquals(this, other);
}

public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}

----------------------------

定義bean

package askyaya.entity;

import java.util.Date;

public class VisitStatInfoBean extends BaseEntity {
/**
*
*/
private static final long serialVersionUID = 1L;
String visitor_ip;
String server_ip;
String page_url;
String parameter;
String Referer_page;
int Visitor_type;
int server_id;
int column_id;
int page_id;
int Visit_count;
int User_id;
int Product_id;
int Seller_id;
Date info_date;
public Date getInfo_date() {
return info_date;
}
public void setInfo_date(Date info_date) {
this.info_date = info_date;
}
public int getColumn_id() {
return column_id;
}
public void setColumn_id(int column_id) {
this.column_id = column_id;
}
public int getPage_id() {
return page_id;
}
public void setPage_id(int page_id) {
this.page_id = page_id;
}
public String getPage_url() {
return page_url;
}
public void setParameter(String parameter) {
this.parameter = parameter;
}
public String getParameter() {
return parameter;
}
public void setPage_url(String parameter) {
this.parameter = parameter;
}

public int getProduct_id() {
return Product_id;
}
public void setProduct_id(int product_id) {
Product_id = product_id;
}
public String getReferer_page() {
return Referer_page;
}
public void setReferer_page(String referer_page) {
Referer_page = referer_page;
}
public int getSeller_id() {
return Seller_id;
}
public void setSeller_id(int seller_id) {
Seller_id = seller_id;
}
public int getServer_id() {
return server_id;
}
public void setServer_id(int server_id) {
this.server_id = server_id;
}
public String getServer_ip() {
return server_ip;
}
public void setServer_ip(String server_ip) {
this.server_ip = server_ip;
}
public int getUser_id() {
return User_id;
}
public void setUser_id(int user_id) {
User_id = user_id;
}
public int getVisit_count() {
return Visit_count;
}
public void setVisit_count(int visit_count) {
Visit_count = visit_count;
}
public String getVisitor_ip() {
return visitor_ip;
}
public void setVisitor_ip(String visitor_ip) {
this.visitor_ip = visitor_ip;
}
public int getVisitor_type() {
return Visitor_type;
}
public void setVisitor_type(int visitor_type) {
Visitor_type = visitor_type;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = super.hashCode();
result = PRIME * result + Product_id;
result = PRIME * result + ((Referer_page == null) ? 0 : Referer_page.hashCode());
result = PRIME * result + Seller_id;
result = PRIME * result + User_id;
result = PRIME * result + Visit_count;
result = PRIME * result + Visitor_type;
result = PRIME * result + column_id;
result = PRIME * result + ((info_date == null) ? 0 : info_date.hashCode());
result = PRIME * result + page_id;
result = PRIME * result + ((page_url == null) ? 0 : page_url.hashCode());
result = PRIME * result + server_id;
result = PRIME * result + ((server_ip == null) ? 0 : server_ip.hashCode());
result = PRIME * result + ((visitor_ip == null) ? 0 : visitor_ip.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
final VisitStatInfoBean other = (VisitStatInfoBean) obj;
if (Product_id != other.Product_id)
return false;
if (Referer_page == null) {
if (other.Referer_page != null)
return false;
} else if (!Referer_page.equals(other.Referer_page))
return false;
if (Seller_id != other.Seller_id)
return false;
if (User_id != other.User_id)
return false;
if (Visit_count != other.Visit_count)
return false;
if (Visitor_type != other.Visitor_type)
return false;
if (column_id != other.column_id)
return false;
if (info_date == null) {
if (other.info_date != null)
return false;
} else if (!info_date.equals(other.info_date))
return false;
if (page_id != other.page_id)
return false;
if (page_url == null) {
if (other.page_url != null)
return false;
} else if (!page_url.equals(other.page_url))
return false;
if (server_id != other.server_id)
return false;
if (server_ip == null) {
if (other.server_ip != null)
return false;
} else if (!server_ip.equals(other.server_ip))
return false;
if (visitor_ip == null) {
if (other.visitor_ip != null)
return false;
} else if (!visitor_ip.equals(other.visitor_ip))
return false;
return true;
}
}
----------------------------



public static void recordVisit_JMS_Info(String visitor_ip,String server_ip,String page_url, String parameter, String Referer_page,
int Visitor_type,int server_id,int column_id,int page_id,int Visit_count,int User_id,
int Product_id,int Seller_id)
{
//從頁面上record_visit.jsp 點擊中,觸發了這個方法
VisitStatInfoBean visitStatInfoBean=new VisitStatInfoBean();
visitStatInfoBean.setVisitor_ip(visitor_ip);
visitStatInfoBean.setServer_ip(server_ip);
visitStatInfoBean.setPage_url(page_url);
visitStatInfoBean.setReferer_page(Referer_page);
visitStatInfoBean.setVisitor_type(Visitor_type);
visitStatInfoBean.setServer_id(server_id);
visitStatInfoBean.setColumn_id(column_id);
visitStatInfoBean.setPage_id(page_id);
visitStatInfoBean.setVisit_count(Visit_count);
visitStatInfoBean.setUser_id(User_id);
visitStatInfoBean.setProduct_id(Product_id);
visitStatInfoBean.setSeller_id(Seller_id);
visitStatInfoBean.setInfo_date(new Date());
//VisitLogSender producer=new VisitLogSender();

try {
VisitLogSender visitLogSender = VisitLogSender.getVisitLogSender();

visitLogSender.sendMessage(visitStatInfoBean);
//producer.close();
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}


}



..............

關鍵點是下面的

VisitLogSender visitLogSender = VisitLogSender.getVisitLogSender(); //單例

visitLogSender.sendMessage(visitStatInfoBean); //發送消息

..........

------------------------

package askyaya.jms;

import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Session;

import org.apache.activemq.ActiveMQConnectionFactory;

import askyaya.entity.VisitStatInfoBean;
import askyaya.visitlog.GetJmsUrl;


/**
* @author 作者 Administrator:金果
* @version 創建時間:
* 類說明:
*/
public class VisitLogSender {

private static String subject = "askyaya.visit.log";

private static Destination destination = null; //消息的目的地

private static Connection connection = null; //JMS 客戶端到JMS Provider 的連接

private static Session session = null; //一個發送或接收消息的線程

private static MessageProducer producer = null; //由Session 對象創建的用來發送消息的對象

private static VisitLogSender visitLogSender = null;

public static VisitLogSender getVisitLogSender(){
if(visitLogSender == null){
visitLogSender = new VisitLogSender();
try{
visitLogSender.initialize();
}catch(Exception e)
{e.printStackTrace();}
}
return visitLogSender;
}

public void initialize() throws JMSException, Exception {

System.out.println("url----------->"+GetJmsUrl.getUrl());

ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory("","",GetJmsUrl.getUrl());

connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //不用事務了,session以自動方式接收,通用性好
destination = session.createQueue(subject);
producer = session.createProducer(destination);

producer.setDeliveryMode(DeliveryMode.PERSISTENT); //以持久的方式到隊列oracle 數據庫

}



//發送消息
public void sendMessage(VisitStatInfoBean message) throws JMSException,Exception {

ObjectMessage msg=session.createObjectMessage();
msg.setObject(message);
System.out.println("Producer:->Sending askyaya_2007 message:"+message);
System.out.println("Producer:->Sending askyaya_2007 msg:"+msg);
producer.send(msg);
System.out.println("Producer:->askyaya_2007 Message sent complete!");

}

// 關閉連接
public void close() throws JMSException {
System.out.println("Producer:->Closing connection");
if (producer != null)
producer.close();
if (session != null)
session.close();
if (connection != null)
connection.close();
}

}

----------------------------------------------

開始收了啊

我是另外建了工程



package askyaya.jms;

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.Session;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;

import askyaya.dao.VisitStatInfoDao;
import askyaya.entity.VisitStatInfoBean;
import askyaya.util.GetJmsUrl;

/**
* @author 作者 Administrator:jinguo
* @version 創建時間:2008-8-30 上午10:55:47
* 類說明:
*/
public class ReceiveVisitLog implements MessageListener{

// private String user =ActiveMQConnection.DEFAULT_USER;

// private String password=ActiveMQConnection.DEFAULT_PASSWORD;

// private String url=ActiveMQConnection.DEFAULT_BROKER_URL;

private static String subject = "askyaya.visit.log";

private static Destination destination = null;

private static Connection connection = null;

private static Session session = null;

private static MessageConsumer consumer = null;



// private MessageProducer replyProducer; //反饋信息

//連接connection,get session
private static ReceiveVisitLog receivevisitlog=null;

public static ReceiveVisitLog getReceiveVisitLog(){
if(receivevisitlog==null){
receivevisitlog=new ReceiveVisitLog();
try {
initialize();
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}
}
return receivevisitlog;
}


private static void initialize() throws JMSException{

GetJmsUrl url=new GetJmsUrl();
//ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(user,password,url);
System.out.println("url---M:"+url.getUrl());


ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory("","",url.getUrl());
System.out.println("connect to tcp:192.168.0.245 ");
Connection connection=connectionFactory.createConnection();
connection.start();

session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
destination=session.createQueue(subject);
// Destination advisoryDestination = AdvisorySupport.getProducerAdvisoryTopic(destination)
consumer=session.createConsumer(destination);

}

// 消費消息
public void consumeMessage() throws JMSException,Exception {

//開始監聽
// MessageListenerForOrgMsg msgListener=new MessageListenerForOrgMsg();
consumer.setMessageListener(this);

System.out.println("Consumer:->after listener message...");

//如果想要去想主動的去接受消息,起用下面的
//Message message=consumer.receiveNoWait();
//TextMessage message=(TextMessage)consumer.receive(1000);

}


// 消息處理函數(onMessage是個構造函數繼承,consumerTool開始執行的時候,先要初始化它,然後才initialze())
//由 container 選擇一個實例
public void onMessage(Message message){
try {

System.out.println("Consumer:->Begin receive message--->:"+message);
if(message!=null){
if(message instanceof ObjectMessage){
ObjectMessage objmsg=(ObjectMessage)message;
//從消息中提取對象,轉化爲bean對象
VisitStatInfoBean visitinfo=(VisitStatInfoBean)objmsg.getObject();

//隔離對數據庫的直接訪問,
VisitStatInfoDao vistdao=new VisitStatInfoDao();
//向數據庫中插入消息
vistdao.insert(visitinfo);
System.out.println("Consumer:->after insert messagedb");
}
}


//消費消息中在onMessage()方法中接收producer發送過來的消息進行處理,並可以通過replyProducer反饋信息給producer
/*if(message.getJMSReplyTo()!=null){
replyProducer.send(message.getJMSReplyTo(),session.createTextMessage("Reply: "+message.getJMSMessageID()));
}*/

} catch (Exception e) {
e.printStackTrace();

}

}

// 關閉連接
public void close() throws JMSException {
System.out.println("Consumer:->Closing connection");
if (consumer != null)
consumer.close();
if (session != null)
session.close();
if (connection != null)
connection.close();
}


public static void main(String[] args) {

//取得接收信息
ReceiveVisitLog receiveVisitLog = ReceiveVisitLog.getReceiveVisitLog();
GetJmsUrl url=new GetJmsUrl();

//接收信息時間間隔
//int sleepTime = 30*1000;
int sleepTime=Integer.parseInt(url.getSleepTime());

while (true){
try {
System.out.println("睡眠" + sleepTime/1000 + "秒之後, 開始接收消息......");
receiveVisitLog.consumeMessage();

Thread.sleep(sleepTime);
System.out.println("接收消息完畢.");

} catch (Exception e) {
e.printStackTrace();

}
}

}


}

--------------------------------

爲了方便大家 我把dao 方法也貼了出來

package askyaya.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import askyaya.entity.VisitStatInfoBean;

public class VisitStatInfoDao {

public void insert(VisitStatInfoBean vfBean)throws SQLException{

Connection ApplicationConn=askyaya.util.DBManager.getconn();
String strsql="insert into tbl_visit_stat_info(visitor_ip,server_id,server_ip,column_id,"
+ "page_id,page_url, parameter, Visit_count,User_id,Visitor_type,Product_id,Seller_id,"
+ "Info_date,visit_date,Referer_page) values(?,?,?,?,?,?,?,?,?,?,?,?,Sysdate,to_char(Sysdate,'yyyy-mm-dd'),?)";

PreparedStatement pstmt = null;
ApplicationConn.setAutoCommit(false);
try {
pstmt = ApplicationConn.prepareStatement(strsql);
//VisitStatInfoBean vfBean=new VisitStatInfoBean();
pstmt.setString(1,vfBean.getVisitor_ip());
pstmt.setInt(2, vfBean.getServer_id());
pstmt.setString(3,vfBean.getServer_ip());
pstmt.setInt(4,vfBean.getColumn_id());
pstmt.setInt(5,vfBean.getPage_id());
pstmt.setString(6,vfBean.getPage_url());
pstmt.setString(7,vfBean.getParameter());
pstmt.setInt(8, vfBean.getVisit_count());
pstmt.setInt(9,vfBean.getUser_id());
pstmt.setInt(10,vfBean.getVisitor_type());
pstmt.setInt(11,vfBean.getProduct_id());
pstmt.setInt(12,vfBean.getSeller_id());
pstmt.setString(13,vfBean.getReferer_page());
pstmt.executeUpdate();
ApplicationConn.commit();
// System.out.println("insert the tbl_visit_stat_info is end ");
} catch (SQLException sqle) {
int errcode = sqle.getErrorCode();
// System.err.println("aq.executeQuery: " + sqle.getMessage()+":;");
//違反唯一約束
if(errcode == 1){}
else{
// System.err.println("aq.executeQuery: " + sqle.getMessage()+":;");
}
ApplicationConn.rollback();
}finally {
try {
pstmt.close();
ApplicationConn.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}

}
}
---------------------------------

得到地址的通用方法

package askyaya.util;

import java.io.InputStream;
import java.util.Properties;



public class GetJmsUrl {

private final static String strUrl=null;
private final static String sleeptime=null;

private final Properties props=new Properties();

private final String url="askyayaJmsConstant.properties";

public GetJmsUrl(){

}

public String getUrl(){
InputStream is=getClass().getResourceAsStream(url);
try {
if(strUrl==null){
props.load(is);
}
} catch (Exception e) {
System.err.println("Can not read the Properties file " + url);
}
String strUrl= props.getProperty("hostname");
return strUrl;

}

public String getSleepTime(){
InputStream is=getClass().getResourceAsStream(url);
try {
if(sleeptime==null){
props.load(is);
}
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}
String strSleepTime= props.getProperty("sleepTime");

return strSleepTime;

}


}
------------------------

askyayaJmsConstant.properties

設置如下:



hostname=tcp://192.168.0.245:61616?wireFormat.maxInactivityDuration=0

sleepTime=30000

---------------

我把服務器的activemq.xml 配置也貼出來把,很關鍵的喲,不懂可以問我.



<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- START SNIPPET: example -->
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd
http://activemq.apache.org/camel/schema/spring http://activemq.apache.org/camel/schema/spring/camel-spring.xsd">

<!-- Allows us to use system properties as variables in this configuration file -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>

<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.base}/data">

<!-- Destination specific policies using destination names or wildcards -->
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry queue=">" memoryLimit="5mb"/>
<policyEntry topic=">" memoryLimit="5mb">
<dispatchPolicy>
<strictOrderDispatchPolicy/>
</dispatchPolicy>
<subscriptionRecoveryPolicy>
<lastImageSubscriptionRecoveryPolicy/>
</subscriptionRecoveryPolicy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>

<!-- Use the following to configure how ActiveMQ is exposed in JMX -->
<managementContext>
<managementContext createConnector="false"/>
</managementContext>

<!-- The store and forward broker networks ActiveMQ will listen to -->
<networkConnectors>
<!-- by default just auto discover the other brokers -->
<networkConnector name="default-nc" uri="multicast://default"/>
<!-- Example of a static configuration:
<networkConnector name="host1 and host2" uri="static://(tcp://host1:61616,tcp://host2:61616)"/>
-->
</networkConnectors>
<!--
<persistenceAdapter>
<amqPersistenceAdapter syncOnWrite="false" directory="${activemq.base}/data" maxFileLength="20 mb"/>
</persistenceAdapter>
-->
<!-- Use the following if you wish to configure the journal with JDBC -->
<!--
<persistenceAdapter>
<journaledJDBC dataDirectory="${activemq.base}/data" dataSource="#postgres-ds"/>
</persistenceAdapter>
-->

<!-- Or if you want to use pure JDBC without a journal -->

<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#oracle-ds"/>
</persistenceAdapter>


<!-- The maximum about of space the broker will use before slowing down producers -->
<systemUsage>
<systemUsage>
<memoryUsage>
<memoryUsage limit="20 mb"/>
</memoryUsage>
<storeUsage>
<storeUsage limit="1 gb" name="foo"/>
</storeUsage>
<tempUsage>
<tempUsage limit="100 mb"/>
</tempUsage>
</systemUsage>
</systemUsage>


<!-- The transport connectors ActiveMQ will listen to -->
<transportConnectors>
<transportConnector name="openwire" uri="tcp://192.168.0.245:61616?wireFormat.maxInactivityDuration=0" discoveryUri="multicast://default"/>
<transportConnector name="ssl" uri="ssl://192.168.0.245:61617"/>
<transportConnector name="stomp" uri="stomp://192.168.0.245:61613"/>
<transportConnector name="xmpp" uri="xmpp://192.168.0.245:61222"/>
</transportConnectors>

</broker>

<!--
** Lets deploy some Enterprise Integration Patterns inside the ActiveMQ Message Broker
** For more details see
**
** http://activemq.apache.org/enterprise-integration-patterns.html
-->
<camelContext id="camel" xmlns="http://activemq.apache.org/camel/schema/spring">

<!-- You can use a <package> element for each root package to search for Java routes -->
<package>org.foo.bar</package>

<!-- You can use Spring XML syntax to define the routes here using the <route> element -->
<route>
<from uri="activemq:example.A"/>
<to uri="activemq:example.B"/>
</route>
</camelContext>


<!-- Uncomment to create a command agent to respond to message based admin commands on the ActiveMQ.Agent topic -->
<!--
<commandAgent xmlns="http://activemq.apache.org/schema/core" brokerUrl="vm://localhost"/>
-->


<!-- An embedded servlet engine for serving up the Admin console -->
<jetty xmlns="http://mortbay.com/schemas/jetty/1.0">
<connectors>
<nioConnector port="8161"/>
</connectors>

<handlers>
<webAppContext contextPath="/admin" resourceBase="${activemq.base}/webapps/admin" logUrlOnStart="true"/>
<webAppContext contextPath="/demo" resourceBase="${activemq.base}/webapps/demo" logUrlOnStart="true"/>
<webAppContext contextPath="/fileserver" resourceBase="${activemq.base}/webapps/fileserver" logUrlOnStart="true"/>
</handlers>
</jetty>

<!-- This xbean configuration file supports all the standard spring xml configuration options -->

<!-- Postgres DataSource Sample Setup -->
<!--
<bean id="postgres-ds" class="org.postgresql.ds.PGPoolingDataSource">
<property name="serverName" value="localhost"/>
<property name="databaseName" value="activemq"/>
<property name="portNumber" value="0"/>
<property name="user" value="activemq"/>
<property name="password" value="activemq"/>
<property name="dataSourceName" value="postgres"/>
<property name="initialConnections" value="1"/>
<property name="maxConnections" value="10"/>
</bean>
-->

<!-- MySql DataSource Sample Setup -->
<!--
<bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/activemq?relaxAutoCommit=true"/>
<property name="username" value="activemq"/>
<property name="password" value="activemq"/>
<property name="maxActive" value="200"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
-->

<!-- Oracle DataSource Sample Setup -->

<bean id="oracle-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@192.168.0.245:1521:askyaya"/>
<property name="username" value="activemq"/>
<property name="password" value="activemq"/>
<property name="maxActive" value="200"/>
<property name="poolPreparedStatements" value="true"/>
</bean>


<!-- Embedded Derby DataSource Sample Setup -->
<!--
<bean id="derby-ds" class="org.apache.derby.jdbc.EmbeddedDataSource">
<property name="databaseName" value="derbydb"/>
<property name="createDatabase" value="create"/>
</bean>
-->

</beans>
<!-- END SNIPPET: example -->



----------------------

以上我用jdbc 來寫的,速度非常快,也可以hibernate實現如果有大蝦知道的話貼出來 ,

最後嗎,有些人問我,什麼樣的人適合做程序員,告訴大家

就是突破能力一定要強,想鍛鍊這能力,多看看書喲.以上實現我只用一個星期就基本就解決了jms 大部分難點.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章