admin管理员组文章数量:1640010
- 前言
第一次在csdn上发布blog,还是有点紧张的,代码写得太差会不会被吐槽,架构不好会不会被喷,总之最后还是抛弃了这些因素,源于一个学长吧,做什么事都有第一次呢,丑媳妇还是要见公婆的,把项目贴出来就是为了能学习一些更好的开发方法,取人之长,补己之短。在这些激烈的探讨过程中,大家一起学习,一起进步,好了,废话不多说了,直接进主题吧。
2.概述
现在大多数访问量比较大的网站都采用了QQ等第三方账号接入系统,QQ一键登录更可减少登录交互操作,大大降低网站注册门槛,给网站带来海量新用户。这里也就不再赘述,具体可以见[QQ接入概述](http://wiki.connect.qq/%E7%BD%91%E7%AB%99%E6%8E%A5%E5%85%A5%E6%A6%82%E8%BF%B0) ,前几天到网上搜了下,关于Java接入QQ登录的文章比较少,大部分都是官方给的API,也有一部分是PHP的,为了能够让一些初学spring的同学能够快速使用spring框架结合QQ官方给的接入API开发出自己的应用系统接入QQ登录的简单解决方案,所以开发了这个小项目,给大家分享。
3.开发前环境准备
1.Spring的基础开发环境,jdk+tomcat+myeclipse+spring,这些就不用说了吧,标配就可以了
2.申请一个免费的域名,本人是使用花生壳来搭建内网服务的,具体申请地址可以参考:[花生壳搭建网站]。目的是能够QQ互联官网申请appid时通过腾讯的网址验证,也就是让其能访问我们的域名(http://service.oray/question/1669.html%20%E4%BD%BF%E7%94%A8%E8%8A%B1%E7%94%9F%E5%A3%B3%E6%90%AD%E5%BB%BA%E7%BD%91%E7%AB%99)
3.开通新花生壳内网版服务,并在服务器电脑上下载新花生壳1.0客户端。
4.[下载新花生壳1.0客户端]并配置好相关映射(http://hsk.oray/download/%20%E4%B8%8B%E8%BD%BD%E6%96%B0%E8%8A%B1%E7%94%9F%E5%A3%B31.0%E5%AE%A2%E6%88%B7%E7%AB%AF)
5.为应用申请appid和appkey,申请地址QQ互联管理中心
注意:1.验证地址时请将其提供的meta代码复制粘贴至你的服务器根目录下的首页,进入tomcat的webapps->ROOT,打开index.jsp,在之间将其提供的meta代码粘贴保存,然后重启tomcat,便可验证成功。
2.回调地址暂时可填与主页相同的地址(在Controller层开发完成后可到QQ互联的管理中心修改) - 验证成功后便可获得appid和appkey,根据此appid和appkey便可着手进行登录业务的代码层面的开发
6.下载QQ官方给的Java SDK:[Java SDK下载](http://wiki.connect.qq/sdk%E4%B8%8B%E8%BD%BD%20Java%20SDK%E4%B8%8B%E8%BD%BD)
下载后的文件结构目录如下:
–sdk4j_demo:官方demo
–sdk4j_doc:官方文档,描述类详情(可以好好看看)
–qqconnectconfig.properties:配置qq登录详细信息的配置文件,配置如下
–Sdk4J.jar 官方jar包,粘贴至工程WEB-INF的libs目录下,并加入到类路径中(必不可少的jar包)
7在myeclipse中新建web工程,目录应该如下所示
关于各类的说明
虽然此次只是开发一个简单的QQ登录功能,但是我们还是 沿用一般spring的架构,采用经典的三层的结构设计,即持久层,业务层,控制层。这样可做到系统模块间相互分离,有利于系统的二次开发,大家在实际开发过程中必定能领悟这种设计的优点,同学们在初学过程中也要仔细体会这种架构的精髓。
下面贴出具体的代码部分,仅供大家参考
com.wuning.constant包(武宁是我的县名,这个项目也是为县大学生共进社开发的)
ConstantQuantity ,提供一些常量的定义,方便引用,包括SQL语句和appid
package com.wuning.constant;
public class ConstantQuantity {
/**
* 根据用户Id查询用户匹配的记录数
*/
public static final String QUERYUSERCOUNT_BY_USER_ID="SELECT COUNT(*) FROM test_user WHERE user_id=?";
/**
* 根据用户OpenId查询用户匹配的记录数
*/
public static final String QUERYUSERCOUNT_BY_TPA_OPENID="SELECT COUNT(*) FROM test_user WHERE user_tpa_openid=?";
/**
* 根据用户Id查询用户信息的SQL语句
*/
public static final String QUERYUSER_BY_USER_ID="SELECT * FROM test_user WHERE user_id=?";
/**
* 根据用户OpenId查询用户信心的SQL语句
*/
public static final String QUERYUSER_BY_USER_TPA_OPENID="SELECT * FROM test_user WHERE user_tpa_openid=?";
/**
* 保存用户信息的语句
*/
public static final String SAVEUSER="INSERT INTO test_user(user_tpa_openid,user_name,user_password,user_createtime,user_image_url)VALUES(?,?,?,?,?)";
/**
* 应用id
*/
public static final String APPID="********";
/**
* 请求地址
*/
public static final String URL="https://graph.qq/user/get_user_info";
}
com.wuning.enity包的User类
实体类,存储用户的相关信息,大家也可以根据自己的项目自行添加或删除一些属性
package com.wuning.enity;
import java.util.Date;
/**
* Enity 用户(待完善)
* @author zhouxuewen
* {@link ...}
*
*/
public class User {
/**
* 用户ID
*/
private int user_id;
/**
* 第三方登录用户ID(例如QQ用户的openID),该ID需持久化到数据库中,方便用户下次登录
*/
private String user_tpa_openid;
/**
* 用户名
*/
private String user_name;
/**
* 用户密码
*/
private String user_password;
/**
* 用户头像url
*/
public String user_image_url;
/**
* 用户注册时间
*/
private Date user_createTime;
public int getUser_id() {
return user_id;
}
public void setUser_id(int user_id) {
this.user_id = user_id;
}
public String getUser_tpa_openid() {
return user_tpa_openid;
}
public void setUser_tpa_openid(String user_tpa_openid) {
this.user_tpa_openid = user_tpa_openid;
}
public String getUser_name() {
return user_name;
}
public void setUser_name(String user_name) {
this.user_name = user_name;
}
public String getUser_password() {
return user_password;
}
public void setUser_password(String user_password) {
this.user_password = user_password;
}
public Date getUser_createTime() {
return user_createTime;
}
public void setUser_createTime(Date user_createTime) {
this.user_createTime = user_createTime;
}
public String getUser_image_url() {
return user_image_url;
}
public void setUser_image_url(String user_image_url) {
this.user_image_url = user_image_url;
}
}
接口类IUserDao,提供用户相关操作的接口类
package com.wuning.dao;
import com.wuning.enity.User;
public interface IUserDao {
/**
* 根据用户ID查询用户是否存在,存在则返回1,不存在则返回0
* @param user_id
* @return int
*/
public int getMatchCountByUserId(int user_id);
/**
* 根据第三方用户的OpenID查询用户是否存在,存在则返回1,不存在则返回0
* @param user_tpa_openid
* @return int
*/
public int getMatchCountByUserTpaId(String user_tpa_openid);
/**
* 根据userid查询用户
* @param user_id
* @return user
*/
public User findUserByUserId(int user_id);
/**
* 根据user_tpa_openid查询用户
* @param user_tpa_openid
* @return user
*/
public User findUserByTpaOpenId(String user_tpa_openid);
/**
* 注册新用户
* @param user
*/
public void saveNewsUser(User user);
}
IUserDao接口实现类UserDaoImp
package com.wuning.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Repository;
import com.wuning.constant.ConstantQuantity;
import com.wuning.enity.User;
/**
* Dao 持久化用户信息数据到数据库中
* @author 18720082476
*
*/
@Repository
public class UserDaoImp implements IUserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public User findUserByUserId(final int user_id){
// TODO Auto-generated method stub
final User user=new User();
jdbcTemplate.query(ConstantQuantity.QUERYUSER_BY_USER_ID, new Object[] {user_id},
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
// 设置用户信息
user.setUser_id(user_id);
user.setUser_tpa_openid(rs.getString("user_tpa_openid"));
user.setUser_name(rs.getString("user_name"));
user.setUser_image_url(rs.getString("user_image_url"));
user.setUser_createTime(rs.getDate("user_createTime"));
}
});
return user;
}
@Override
public User findUserByTpaOpenId(final String user_tpa_openid) {
// TODO Auto-generated method stub
final User user=new User();
jdbcTemplate.query(ConstantQuantity.QUERYUSER_BY_USER_TPA_OPENID, new Object[] {user_tpa_openid},
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
// 设置用户信息
user.setUser_id(rs.getInt("user_id"));
user.setUser_tpa_openid(user_tpa_openid);
user.setUser_name(rs.getString("user_name"));
user.setUser_image_url(rs.getString("user_image_url"));
user.setUser_createTime(rs.getDate("user_createTime"));
}
});
return user;
}
@Override
public void saveNewsUser(User user) {
// TODO Auto-generated method stub
jdbcTemplate.update(ConstantQuantity.SAVEUSER, new Object[]{user.getUser_tpa_openid(),user.getUser_name(),user.getUser_password(),user.getUser_createTime(),user.getUser_image_url()});
}
@Override
public int getMatchCountByUserId(int user_id) {
// TODO Auto-generated method stub
return jdbcTemplate.queryForInt(ConstantQuantity.QUERYUSERCOUNT_BY_USER_ID, new Object[]{user_id});
}
@Override
public int getMatchCountByUserTpaId(String user_tpa_openid) {
// TODO Auto-generated method stub
return jdbcTemplate.queryForInt(ConstantQuantity.QUERYUSERCOUNT_BY_TPA_OPENID, new Object[]{user_tpa_openid});
}
}
用户业务的接口定义类IUserService
package com.wuning.service;
import javax.servlet.http.HttpServletRequest;
import com.qq.connect.QQConnectException;
import com.wuning.enity.User;
/**
* 用户Service
* @author 18720082476
*
*/
public interface IUserService {
/**
* 验证第三方应用授权的用户是否已存在用户列表中
* @param user_tpa_openid
* @return
*/
public int getMatchCountByUserTpaId(String user_tpa_openid);
/**
* 根据user_tpa_openid查询用户信息并返回User实例
* @param user_tpa_openid
* @return
*/
public User findUserByTpaOpenId(String user_tpa_openid);
/**
* 注册用户
* @param user
*/
public void saveNewUser(User user);
//QQ登录
/**
* 根据授权成功后返回的redirect_uri地址后带的Authorization Code和原始的state值解析Access Token
* 进而获取opendID,构造用户信息并返回
* @param request
* @return
*/
public User getUserByOAuthOfQQ(HttpServletRequest request)throws QQConnectException;
//新浪微博登陆
//未实现
}
用户登录业务的实现类UserLoginServiceImp
package com.wuning.service;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import com.qq.connect.QQConnectException;
import com.qq.connect.api.OpenID;
import com.qq.connect.api.qzone.UserInfo;
import com.qq.connect.javabeans.AccessToken;
import com.qq.connect.javabeans.qzone.UserInfoBean;
import com.qq.connect.oauth.Oauth;
import com.wuning.constant.ConstantQuantity;
import com.wuning.dao.UserDaoImp;
import com.wuning.enity.User;
import com.wuning.util.HttpUtil;
@Service
public class UserLoginServiceImp implements IUserService {
@Autowired
private UserDaoImp userDaoImp;
@Override
public int getMatchCountByUserTpaId(String user_tpa_openid) {
// TODO Auto-generated method stub
return userDaoImp.getMatchCountByUserTpaId(user_tpa_openid);
}
@Override
public User findUserByTpaOpenId(String user_tpa_openid) {
// TODO Auto-generated method stub
return userDaoImp.findUserByTpaOpenId(user_tpa_openid);
}
@Override
public void saveNewUser(User user) {
// TODO Auto-generated method stub
userDaoImp.saveNewsUser(user);
}
@Override
public User getUserByOAuthOfQQ(HttpServletRequest request) throws QQConnectException {
// TODO Auto-generated method stub
final User user=new User();
String accessToken=null,
openID=null;
long tokenExpireIn = 0L;
//--------------官方代码-----------------------------------------------
//以下代码由腾讯官方Java SDK给出,详见:http://wiki.connect.qq/sdk下载,所用到的
//类也均在腾讯官方给的Sdk4J.jar中给出
//根据返回的request解析出AccessToken对象
AccessToken accessTokenObj = (new Oauth()).getAccessTokenByRequest(request);
if (accessTokenObj.getAccessToken().equals("")) {
//我们的网站被CSRF攻击了或者用户取消了授权
//做一些数据统计工作
System.out.print("没有获取到响应参数");
} else {
//获取accessToken信息
accessToken = accessTokenObj.getAccessToken();
tokenExpireIn = accessTokenObj.getExpireIn();
// 利用获取到的accessToken 去获取当前用的openid -------- start
OpenID openIDObj = new OpenID(accessToken);
//获取openid
openID = openIDObj.getUserOpenID();
System.out.println("欢迎你,代号为 " + openID + " 的用户!");
//根据accessToken和openID获取UserInfo对象
UserInfo qzoneUserInfo = new UserInfo(accessToken, openID);
//获取bean
UserInfoBean userInfoBean = qzoneUserInfo.getUserInfo();
//---------------------------官方代码结束----------------------------
//用户信息获取完成,接下来根据我们应用的具体情况获取用户在QQ上的资料,构造自己的User
/*ret 返回码
msg 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码。
nickname 用户在QQ空间的昵称。
figureurl 大小为30×30像素的QQ空间头像URL。
figureurl_1 大小为50×50像素的QQ空间头像URL。
figureurl_2 大小为100×100像素的QQ空间头像URL。
figureurl_qq_1 大小为40×40像素的QQ头像URL。
figureurl_qq_2 大小为100×100像素的QQ头像URL。需要注意,不是所有的用户都拥有QQ的100x100的头像,但40x40像素则是一定会有。
gender 性别。 如果获取不到则默认返回"男"
is_yellow_vip 标识用户是否为黄钻用户(0:不是;1:是)。
vip 标识用户是否为黄钻用户(0:不是;1:是)
yellow_vip_level 黄钻等级
level 黄钻等级
is_yellow_year_vip 标识是否为年费黄钻用户(0:不是; 1:是) */
//设置用户名为QQ用户在QQ空间的昵称
user.setUser_name(userInfoBean.getNickname());
//设置用户的openid为获取的openid
user.setUser_tpa_openid(openID);
//设置用户的头像地址,该头像来自QQ空间的头像
//user.setUser_image_url(userInfoBean.getAvatar().getAvatarURL50());
System.out.println("第一个头像地址:"+userInfoBean.getAvatar().getAvatarURL50());
//设置用户的头像地址,该头像来自QQ头像
String user_img_url=HttpUtil.sendGet(ConstantQuantity.URL, ConstantQuantity.APPID, accessToken, openID);
user.setUser_image_url(user_img_url);
System.out.println("第二个头像地址:"+user_img_url);
//设置用户密码为空
user.setUser_password("");
//设置创建时间
user.setUser_createTime(new Date());
}
return user;
}
}
UserLoginService的核心方法即为getUserByOAuthOfQQ,通过该方法我们获取到用户QQ的相关资料信息,并返回User给控制层调用,需要注意的是,在设置用户头像url的时候有两种选择,一是可以设置为用户的QQ空间头像,直接调用userInfoBean.getAvatar().getAvatarURL50()返回头像地址即可,二是可以设置为用户的QQ头像,由于在官方的给的API中并没有给出获取QQ头像的调用方法,我这里采用的是根据腾讯官方提供的API,利用http请求获取用户的信息,调用该接口返回的字符串数据中包含QQ头像的url,于是分析该字符串便可以得到QQ头像的url,设置到用户的image属性中。这里大家可以根据自己应用来做不同的选择.以下是Http实现类,参考了java实现http GET/POST请求
package com.wuning.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.URL;
import java.URLConnection;
import java.util.List;
import java.util.Map;
import net.sf.json.JSONArray;
public class HttpUtil {
/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String appid,String access_token,String opendid) {
String result = "";
String param="oauth_consumer_key="+appid+"&access_token="+access_token+"&openid="+opendid+"&format=json";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
//Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
/* for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}*/
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
//去掉字符串中的反斜杠\和空格
String rsFormat=result.replaceAll("\\\\", "").replaceAll(" ", "");
//将字符串格式化为数组
String[] rsArray=rsFormat.split("");
/**
* 算法思想
* 根据测试,在返回的数据中,QQ头像的url地址在figureurl_qq_1后,且不同QQ用户的头像地址的长度固定,均为69
* 于是便可设计算法,先去掉"\"和空格后,利用test字符串作为辅助字符串,test=test+rsArray[i];,当test内包含"
* figureurl_qq_1"时,记录此时test的长度,往后延长三位便是头像地址的信息开头,即http://......,此时结束循环
* 利用subString(startindex,startindex+69)截取到url,返回
*
*/
String auxiliaryString="";
int startindex=0;
for(int i=0;i<rsArray.length;i++){
auxiliaryString=auxiliaryString+rsArray[i];
if(auxiliaryString.contains("figureurl_qq_1")){
startindex=test.length()+3;
break;
}
}
//释放资源
auxiliaryString=null;
rsArray=null;
return rsFormat.substring(startindex,startindex+69);
}
接下来是控制层代码,由于实现业务简单,所以只有一个Controller
package com.wuning.web;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.qq.connect.QQConnectException;
import com.qq.connect.oauth.Oauth;
import com.wuning.enity.User;
import com.wuning.service.UserLoginServiceImp;
@Controller
public class mainController {
@Autowired
private UserLoginServiceImp userLoginServiceImp;
/**
* 首页请求
* @return
*/
@RequestMapping(value="/homepage")
public String homePage() {
return "main";
}
/**
* 处理QQ登录请求
* 1.设置响应格式
* 2.将请求重定向至QQ登录授权页面
* @param request
* @param response
* @throws IOException
*/
@RequestMapping(value="/qqlogin",method=RequestMethod.GET)
public void tologin(HttpServletRequest request, HttpServletResponse response) throws IOException{
response.setContentType("text/html;charset=utf-8");
try {
response.sendRedirect(new Oauth().getAuthorizeURL(request));
} catch (QQConnectException e) {
e.printStackTrace();
}
}
/**
* 处理QQ登录成功后回调请求
* 1.根据请求后回调地址后参数解析出openid
* 2.根据openid构造相应用户信息
* 3.判断用户是否为已经注册用户
* 4.如果是已经注册用户,则直接保存用户到session中,重定向回首页,如果未注册用户,则提示用户注册,
* 此处是保存新用户信息到数据库中(相当于注册),然后重定向至首页
* 核心:User user=userLoginServiceImp.getUserByOAuthOfQQ(request);
* @param request
* @return String
* @throws QQConnectException
* @throws IOException
*/
@RequestMapping(value="/qqloginback")
public String loginback(HttpServletRequest request)throws QQConnectException, IOException{
System.out.println("请求地址:"+request.getQueryString());
User user=userLoginServiceImp.getUserByOAuthOfQQ(request);
//此时如果将用户信息全部存于session中,是不安全的,希望在具体应用时,只需要将需要展示的用户信息的经过包装的用户对象存于session中
//此处是为了简单起见
request.getSession().setAttribute("user", user);
int count=userLoginServiceImp.getMatchCountByUserTpaId(user.getUser_tpa_openid());
System.out.println(count);
//如果用户已经注册
if(count==1){
//返回首页
return "redirect:homepage";
}
else {
//否则保存用户信息到数据库中
userLoginServiceImp.saveNewUser(user);
//添加具体的业务代码,如记录到日志中
//。。。。。。。。。
//或者将用户信息保存到ModelAndView中,到注册页将信息中取出来,避免用户二次输入
//ModelAndView modelAndView=new ModelAndView();
//modelAndView.setViewName("redirect:register");
//modelAndView.addObject("user", user);
//return modelAndView;
//直接返回到首页
return "redirect:homepage";
}
}
/**
* 处理用户注销请求
* 1.将用户对象从session中移除
* 2.重定向至首页
* @param session
* @return
*/
@RequestMapping(value="/qqloginOut")
public String qqloginOut(HttpSession session){
session.removeAttribute("user");
return "redirect:homepage";
}
}
mainController说明
1.主要有三个处理请求的方法,
1.1 homePage() 请求url为:”/homepage”,处理首页请求,即在浏览器输入:你申请的域名/项目名称/homepage,如:www.zhouxuewen.iego/wuning/homepage即可浏览该项目首页
1.2 tologin() 请求url为:/qqlogin”,当用户点击登录按钮后,向本地服务器发送此请求,tologin()接收请求后,将请求重定向至QQ登录的授权页面。
页面的登录按钮
“
1.3 loginback() 请求url为:”/qqloginback”当QQ用户在授权页面点击登录后,页面会自动将地址重定向为你申请appid时所填的回调地址,回调地址后已经携带了获取openid的参数信息,并向本地服务器发起http请求,请求被 loginback()接收后处理。利用User user=userLoginServiceImp.getUserByOAuthOfQQ(request)获取用户信息,并到数据库中查询判断该用户是否为第一次登录,若为第一次登录,则保存用户信息到数据库中,否则重定向回首页
此时QQ登录的回调地址则为:你申请的域名/项目名称/qqloginback,这个地址也是你必须要填入qqconnectconfig.properties
和QQ互联管理中心的应用回调地址中,如我的回调地址为:http://www.zhouxuewen.iego/wuning/qqloginback.
这个地址必须在入qqconnectconfig.properties文件和管理中心的应用回调地址和Controller的处理请求的回调地址保持一致,否则将会出现请求地址错误的情况。
1.4 登录后的用户的注销操作
项目运行时效果如下:
关于项目的代码,我也会分享到csdn,这次是QQ登录的,下次将会继续退出新浪微博登录的项目,敬请期待!
版权声明:本文标题:Spring框架开发QQ登录教程 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dianzi/1729305552a1195029.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论