ToB企服应用市场:ToB评测及商务社交产业平台
标题:
基于 SpringBoot + MyBatis 的博客系统
[打印本页]
作者:
伤心客
时间:
2022-6-26 03:13
标题:
基于 SpringBoot + MyBatis 的博客系统
文章目录
1. 项目设计
2. 效果展示
3. 创建项目并配置文件
1.1 创建 Spring 项目
1.2 配置文件
4. 数据库实现用户和博客管理
4.1 设计数据库
4.2 使用 MyBatis 操作数据库
UserMapper.xml
BlogMapper.xml
User 实体类 和 Blog 实体类
UserMapper 接口 和 BlogMapper 接口
UserService 类 和 BlogService 类
5. 前后端交互接口设计
6. 导入前端代码
7. 实现博客主页
7.1 实现后端代码
7.2 实现前端代码
7.3 测试代码
7.4 解决页面内容太多超出当前浏览器
7.5 解决页面顺序不是按最新时间排序
7.6 解决内容太多, 导致显示的时候占位太多.
7.7 再次测试代码
8. 实现博客详情页
8.1 实现后端代码
8.2 实现前端代码
8.3 测试代码
8.4 这里展示为markdown语法的正文
9. 实现博客登录界面
9.1 实现后端代码
9.2 实现前端代码
10. 实现登录判断 --- 拦截器
10.1 实现自定义拦截器
10.2 将自定义拦截器加入到系统配置
11. 实现注册功能
11.1 实现后端代码
11.2 实现前端代码
12. 实现注销功能
12.1 实现后端代码
13. 实现博客编辑页
13.1 实现后端代码
13.2 实现前端代码
14. 实现博客个人主页
14.1 实现后端代码
14.2 实现前端代码
15. 实现展示用户信息的功能
15.1 实现后端代码
15.2 实现前端代码
16. 实现博客的删除功能
16.1 改进代码
16.2 实现后端代码
17. 实现博客的修改功能
17.1 实现后端代码
17.2 实现前端代码
1. 项目设计
前端
使用 HTML+CSS+JavaScript+JQuery
后端
使用 Spring MVC+Spring Boot+MyBatis
2. 效果展示
3. 创建项目并配置文件
1.1 创建 Spring 项目
1.2 配置文件
application.properties 配置内容
spring.profiles.active=dev
复制代码
application-dev.properties 配置内容
spring.datasource.url=jdbc:mysql://localhost:3306/MyBlogSystem?characterEncoding=utf8&useSSL=true
spring.datasource.username=root
spring.datasource.password=0000
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
复制代码
4. 数据库实现用户和博客管理
4.1 设计数据库
这里博客系统, 是一个用户表和博客表,
用户一般分为:
用户Id (每个人一个且互不相同)
用户名 (每个人的用户名不相同)
密码
用户头像(暂不实现)
用户马云地址(暂不实现)
博客一般分为:
博客Id (每篇博客一个且互不相同)
标题
正文
创建时间
修改时间 (这里没用到, 自己想加也可以)
用户Id (可以设置一个外键,这里没设置)
create database if not exists MyBlogSystem;
use MyBlogSystem;
drop table if exists blog;
-- 创建一个博客表
create table blog (
blogId int primary key auto_increment,
title varchar(1024),
content mediumtext,
postTime datetime,
userId int
);
drop table if exists user;
-- 创建一个用户信息表
create table user (
userId int primary key auto_increment,
username varchar(128) unique,
password varchar(128)
);
复制代码
4.2 使用 MyBatis 操作数据库
在 resources 下创建一个 mapper 包. 包下创建 UserMapper.xml 和
BlogMapper.xml
BlogMapper.xml 里是对博客表的数据库操作
UserMapper.xml 里是对用户表的数据库操作
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<insert id="addUser" keyProperty="userId" keyColumn="userId">
insert into user(username password) values (#{username}, #{password})
</insert>
<select id="selectByName" resultType="com.example.demo.model.User">
select * from user where username = #{username}
</select>
<select id="selectById" resultType="com.example.demo.model.User">
select * from user where userId = #{userId}
</select>
</mapper>
复制代码
BlogMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.BlogMapper">
<select id="getAllBlog" resultType="com.example.demo.model.Blog">
select * from blog
</select>
<select id="getBlogByBid" resultType="com.example.demo.model.Blog">
select * from blog where blogId = #{blogId}
</select>
<insert id="postBlog" keyColumn="userId" keyProperty="userId">
insert into blog(title,content,postTime,userId) values(#{title},#{content},#{postTime},#{userId})
</insert>
<delete id="deleteBlog">
delete from blog where blogId = #{blogId}
</delete>
<update id="updateBlog">
update blog set content = #{content},title = #{title} where blogId = #{blogId}
</update>
<select id="getAllBlogById" resultType="com.example.demo.model.Blog">
select * from blog where userId = #{userId}
</select>
</mapper>
复制代码
User 实体类 和 Blog 实体类
在 model 包下 创建 User 类 和 Blog 类
User 类
@Data
public class User {
public int userId;
public String username;
public String password;
}
复制代码
Blog 类
@Data
public class Blog {
public int blogId;
public String title;
public String content;
public Timestamp postTime;
public int userId;
}
复制代码
UserMapper 接口 和 BlogMapper 接口
在 mapper 包下创建 UserMapper 和 BlogMapper 接口
UserMapper
@Mapper
public interface UserMapper {
void addUser(User user);
User selectByName(String username);
User selectById(Integer userId);
}
复制代码
BlogMapper
@Mapper
public interface BlogMapper {
List<Blog> getAllBlog();
Blog getBlogByBid(Integer blogId);
void postBlog(Blog blog);
void deleteBlog(Integer blogId);
void updateBlog(Blog blog);
List<Blog> getAllBlogById();
}
复制代码
UserService 类 和 BlogService 类
service 包下创建
UserService类
和
BlogService类
UserService
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public void addUser(User user){
userMapper.addUser(user);
}
public User selectByName(String username){
return userMapper.selectByName(username);
}
public User selectById(Integer userId){
return userMapper.selectById(userId);
}
}
复制代码
BlogService
@Service
public class BlogService {
@Resource
private BlogMapper blogMapper;
public List<Blog> getAllBlog(){
return blogMapper.getAllBlog();
}
public Blog getBlogByBid(Integer blogId){
return blogMapper.getBlogByBid(blogId);
}
public void postBlog(Blog blog) {
blogMapper.postBlog(blog);
}
public void deleteBlog(Integer blogId){
blogMapper.deleteBlog(blogId);
}
public void updateBlog(Blog blog){
blogMapper.updateBlog(blog);
}
public List<Blog> getAllBlogById(){
return blogMapper.getAllBlogById();
}
}
复制代码
5. 前后端交互接口设计
交互1
交互2
交互3
交互4
交互5
交互6
交互7
交互8
交互9
交互10
交互11
6. 导入前端代码
导入前端代码到 resources 的 static下
具体代码查看 文章
博客系统前端界面
https://wangzhi430.blog.csdn.net/article/details/124649884
7. 实现博客主页
这里的交互接口是 交互6
7.1 实现后端代码
@RestController
public class IndexController {
@Autowired
private BlogService blogService;
@RequestMapping("/index")
public List<Blog> getAllBlog() {
return blogService.getAllBlog();
}
}
复制代码
7.2 实现前端代码
$.ajax({
url: "index",
method: "GET",
success: function(data,status) {
buildBlogs(data);
}
})
function buildBlogs(blogs){
let rightDiv = document.querySelector('.right');
for(let blog of blogs){
let blogDiv = document.createElement('div');
blogDiv.className = 'article';
// 创建 title
let h2 = document.createElement('h2');
h2.className = 'title';
h2.innerHTML = blog.title;
blogDiv.appendChild(h2);
// 创建 postTime
let postTime = document.createElement('span');
postTime.className = 'date';
postTime.innerHTML = DateFormat(blog.postTime);
blogDiv.appendChild(postTime);
// 创建 content
let content = document.createElement('div');
content.className = 'desc';
content.innerHTML = blog.content;
blogDiv.appendChild(content);
// 创建 详情页的超链接
let detailA = document.createElement('a');
detailA.className = 'more';
detailA.href = 'art.html?blogId=' + blog.blogId;
detailA.innerHTML = '查看全文>>';
blogDiv.appendChild(detailA);
// 加入到 right 中
rightDiv.appendChild(blogDiv);
}
}
// 把毫秒级时间戳转化成格式化日期
function DateFormat(timeStampMS) {
var date = new Date(timeStampMS);
var year = date.getFullYear(),
month = date.getMonth()+1,//月份是从0开始的
day = date.getDate(),
hour = date.getHours(),
min = date.getMinutes(),
sec = date.getSeconds();
var newTime = year + '-' +
(month < 10? '0' + month : month) + '-' +
(day < 10? '0' + day : day) + ' ' +
(hour < 10? '0' + hour : hour) + ':' +
(min < 10? '0' + min : min) + ':' +
(sec < 10? '0' + sec : sec);
return newTime;
}
复制代码
7.3 测试代码
7.4 解决页面内容太多超出当前浏览器
7.5 解决页面顺序不是按最新时间排序
在BlogMapper.xml中修改当前sql语句.
7.6 解决内容太多, 导致显示的时候占位太多.
7.7 再次测试代码
8. 实现博客详情页
这里的交互是 交互5
8.1 实现后端代码
根据当前blogId来获取文章, 要判断blogId是否合法, 是否存在当前blogId的文章
这里使用, message来判断, 返回的message不为空就表示异常.
@RestController
public class DetailsController {
@Autowired
private BlogService blogService;
@RequestMapping("/details")
public Object ShowBlog(Integer blogId) {
HashMap<String,Object> map = new HashMap<>();
if (blogId == null || blogId < 1) {
map.put("message","blogId异常!");
return map;
}
Blog blog = blogService.getBlogByBid(blogId);
if (blog == null) {
map.put("message","不存在当前blogId的文章");
return map;
}
return blog;
}
}
复制代码
8.2 实现前端代码
$.ajax({
url: "details"+location.search,
method: "GET",
success: function(data,status) {
if(data.message == null) {
buildBlog(data);
} else {
alert(data.message);
location.assign("home.html");
}
}
})
function buildBlog(blog){
// 1. 更新 title
let titleDiv = document.querySelector('.title');
titleDiv.innerHTML = blog.title;
// 2. 更新 postTime
let postTime = document.querySelector('.date');
postTime.innerHTML = DateFormat(blog.postTime);
editormd.markdownToHTML('content', {markdown: blog.content});
}
// 把毫秒级时间戳转化成格式化日期
function DateFormat(timeStampMS) {
var date = new Date(timeStampMS);
var year = date.getFullYear(),
month = date.getMonth()+1,//月份是从0开始的
day = date.getDate(),
hour = date.getHours(),
min = date.getMinutes(),
sec = date.getSeconds();
var newTime = year + '-' +
(month < 10? '0' + month : month) + '-' +
(day < 10? '0' + day : day) + ' ' +
(hour < 10? '0' + hour : hour) + ':' +
(min < 10? '0' + min : min) + ':' +
(sec < 10? '0' + sec : sec);
return newTime;
}
复制代码
8.3 测试代码
8.4 这里展示为markdown语法的正文
展示为 markdown 语法的正文.
注意这里的几段代码
这里还需要导入依赖包
9. 实现博客登录界面
这里的交互是 交互8
9.1 实现后端代码
这里要根据前端穿过来的 json 格式数据进行判断
判断当前是否存在用户, 以及当前用户密码是否正确
登录成功之后, 要创建一个session
@RestController
public class LoginController {
@Autowired
private UserService userService;
@RequestMapping("/login")
public Object userLogin(@RequestBody User user, HttpServletRequest request) {
HashMap<String,Object> map = new HashMap<>();
if (user == null) {
map.put("message","当前还没有输入用户名和密码,登录失败!");
return map;
}
User user1 = userService.selectByName(user.getUsername());
if (user1 == null) {
map.put("message","当前用户名不存在!");
return map;
}
if (!user.getPassword().equals(user1.getPassword())) {
map.put("message","当前用户名密码错误!");
return map;
}
user.setUserId(user1.getUserId());
HttpSession session = request.getSession(true);
if (session != null) {
session.setAttribute("user",user);
}
return map;
}
}
复制代码
9.2 实现前端代码
这里前端去除了前后空格,以及为空的情况
let submit = document.querySelector('.button');
submit.onclick = function() {
let username = document.querySelector('.user');
let password = document.querySelector('.password');
if (username.value.trim() == ""){
alert('请先输入用户名!');
username.focus();
return;
}
if (password.value.trim() == ""){
alert('请先输入密码!');
password.focus();
return;
}
$.ajax({
url: "login",
method: "POST",
data: JSON.stringify({username: username.value.trim(), password: password.value.trim()}),
contentType: "application/json;charset=utf-8",
success: function(data, status) {
if(data.message == null) {
location.assign("home.html");
}else{
alert(data.message);
username.value="";
password.value="";
username.focus();
}
}
})
}
复制代码
10. 实现登录判断 — 拦截器
10.1 实现自定义拦截器
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("user") != null) {
return true;
}
response.setStatus(401);
response.sendRedirect("/login.html");
return false;
}
}
复制代码
10.2 将自定义拦截器加入到系统配置
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/**/*.js")
.excludePathPatterns("/**/*.jpg")
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.png")
.excludePathPatterns("/**/login.html")
.excludePathPatterns("/**/register.html")
.excludePathPatterns("/**/login")
.excludePathPatterns("/**/register");
}
}
复制代码
11. 实现注册功能
这里的交互是 交互9
11.1 实现后端代码
实现一个 类 Register 来接收前端返回来的数据
class Register {
public String username;
public String password1;
public String password2;
}
复制代码
这里后端需要判断当前用户名是否已经被使用.
@RestController
public class RegisterController {
@Autowired
private UserService userService;
@RequestMapping("/register")
public Object userRegister(@RequestBody Register register) {
HashMap<String,Object> map = new HashMap<>();
User user = userService.selectByName(register.username);
if (user != null) {
map.put("message","当前用户名已经存在了, 请更换!");
return map;
}
User user1 = new User();
user1.setUsername(register.username);
user1.setPassword(register.password1);
userService.addUser(user1);
return map;
}
}
复制代码
11.2 实现前端代码
要对输入内容去除前后空格,并且判空
let submit = document.querySelector('.button');
submit.onclick = function() {
let username = document.querySelector('.user');
let password1 = document.querySelector('.password1');
let password2 = document.querySelector('.password2');
if(username.value.trim() == ""){
alert("请先输入用户名!");
username.focus();
return;
}
if(password1.value.trim() == ""){
alert('请先输入密码!');
password1.focus();
return;
}
if(password2.value.trim() == ""){
alert('请再次输入密码!');
password2.focus();
return;
}
if(password1.value.trim() != password2.value.trim()) {
alert('两次输入的密码不同!');
passwrod1.value="";
password2.value="";
return;
}
$.ajax({
url: "register",
method: "POST",
data: JSON.stringify({username: username.value.trim(), password1: password1.value.trim(),password2: password2.value.trim()}),
contentType: "application/json;charset=utf-8",
success: function(data,status){
if(data.message != null){
alert(data.message);
username.value="";
password1.value="";
password2.value="";
username.focus();
}else{
location.assign('login.html');
}
}
})
}
复制代码
12. 实现注销功能
这里的交互是 交互10
12.1 实现后端代码
因为 注销功能是点击注销的时候, 触发一个logout的url, 然后发送一个请求.
这里只需要实现后端代码既可
@Controller
public class LogoutController {
@RequestMapping("/logout")
public void userLogout(HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpSession session = request.getSession(false);
// 拦截器的拦截, 所以不可能出现session为空的情况
session.removeAttribute("user");
response.sendRedirect("login.html");
}
}
复制代码
13. 实现博客编辑页
这里的交互是 交互1
13.1 实现后端代码
@RestController
public class EditController {
@Autowired
private BlogService blogService;
@RequestMapping("/edit")
public void postBlog(@RequestBody Blog blog, @SessionAttribute(value = "user",required = false)User user){
blog.setPostTime(new Timestamp(System.currentTimeMillis()));
blog.setUserId(user.getUserId());
blogService.postBlog(blog);
}
}
复制代码
13.2 实现前端代码
let submit = document.querySelector('.publish');
submit.onclick = function() {
let title = document.querySelector('.title');
let content = document.querySelector('.content');
if(title.value.trim() == ""){
alert('当前文章标题为空,请输入!');
title.focus();
return;
}
if(content.value.trim() == ""){
alert('当前文章内容为空,请输入!');
content.focus();
return;
}
$.ajax({
url: "edit",
method: "POST",
data: JSON.stringify({title: title.value.trim(), content: content.value.trim()}),
contentType: "application/json;charset=utf-8",
success: function(data,status) {
location.assign('home.html');
}
})
}
复制代码
14. 实现博客个人主页
这里的交互是 交互7
这里的前端页面主要就是主页页面的改进
14.1 实现后端代码
@RestController
public class PersonController {
@Autowired
private BlogService blogService;
@RequestMapping("/person")
public List<Blog> getMyBlog(@SessionAttribute(value = "user",required = false)User user) {
List<Blog> blogs = blogService.getAllBlogById(user.getUserId());
for (Blog blog : blogs) {
if (blog.getContent().length() > 80) {
blog.setContent(blog.getContent().substring(0,80) + " ...");
}
}
return blogs;
}
}
复制代码
14.2 实现前端代码
$.ajax({
url: "person",
method: "GET",
success: function(data,status) {
buildBlogs(data);
}
})
function buildBlogs(blogs){
let rightDiv = document.querySelector('.right');
for(let blog of blogs){
let blogDiv = document.createElement('div');
blogDiv.className = 'article';
// 创建 title
let h2 = document.createElement('h2');
h2.className = 'title';
h2.innerHTML = blog.title;
blogDiv.appendChild(h2);
// 创建 postTime
let postTime = document.createElement('span');
postTime.className = 'date';
postTime.innerHTML = DateFormat(blog.postTime);
blogDiv.appendChild(postTime);
// 创建 content
let content = document.createElement('div');
content.className = 'desc';
content.innerHTML = blog.content;
blogDiv.appendChild(content);
// 创建 详情页的超链接
let detailA = document.createElement('a');
detailA.className = 'more';
detailA.href = 'art.html?blogId=' + blog.blogId;
detailA.innerHTML = '查看全文>>';
blogDiv.appendChild(detailA);
// 加入到 right 中
rightDiv.appendChild(blogDiv);
}
}
// 把毫秒级时间戳转化成格式化日期
function DateFormat(timeStampMS) {
var date = new Date(timeStampMS);
var year = date.getFullYear(),
month = date.getMonth()+1,//月份是从0开始的
day = date.getDate(),
hour = date.getHours(),
min = date.getMinutes(),
sec = date.getSeconds();
var newTime = year + '-' +
(month < 10? '0' + month : month) + '-' +
(day < 10? '0' + day : day) + ' ' +
(hour < 10? '0' + hour : hour) + ':' +
(min < 10? '0' + min : min) + ':' +
(sec < 10? '0' + sec : sec);
return newTime;
}
复制代码
15. 实现展示用户信息的功能
这里的交互是 交互11
这里需要分情况考虑, 展示个人信息主要是 主页页面, 详情页面, 个人主页页面.
以带不带blogId来区分
15.1 实现后端代码
这里判断了 blogId丢失的情况以及,文章作者丢失情况(数据库表数据被删除的时候会出现这种错误)
@RestController
public class UserController {
@Autowired
private UserService userService;
@Autowired
private BlogService blogService;
@RequestMapping("/user")
public Object getUser(Integer blogId, @SessionAttribute(value = "user",required = false)User user){
if(blogId == null) {
return user;
}else {
HashMap<String,Object> map = new HashMap<>();
Blog blog = blogService.getBlogByBid(blogId);
if(blog == null) {
map.put("message","不存在当前blogId的文章");
return map;
}
User author = userService.selectById(blog.getUserId());
if(author == null){
map.put("message","当前文章作者出错");
return map;
}
return author;
}
}
}
复制代码
15.2 实现前端代码
详情页的情况:
$.ajax({
url: "user"+location.search,
method: "GET",
success: function(data,status) {
if(data.message == null){
let username = document.querySelector('.name');
username.innerHTML = data.username;
}else{
alert(data.message);
location.assign('home.html');
}
}
})
复制代码
个人主页和主页的情况
$.ajax({
url: "user",
method: "GET",
success: function(data,status){
let username = document.querySelector('.name');
username.innerHTML = data.username;
}
})
复制代码
16. 实现博客的删除功能
这里需要用到 交互2
这里在详情页的时候进行构建, 在Blog实体类中加一项 isAuthor, 为1的时候就是当前文章就是作者.
前端接收到这个的时候, 进行判断, 如果为1就显示删除的按钮.
16.1 改进代码
16.2 实现后端代码
@Controller
public class DeleteController {
@Autowired
private BlogService blogService;
@RequestMapping("/delete")
public Object deleteBlog(Integer blogId) {
blogService.deleteBlog(blogId);
return "/home.html";
}
}
复制代码
17. 实现博客的修改功能
这里的交互是 交互3 和 交互4
交互3是在新的页面进行加载
17.1 实现后端代码
@RestController
public class UpdateController {
@Autowired
private BlogService blogService;
@RequestMapping("/updateLoad")
public Object updateLoad(Integer blogId){
HashMap<String, Object> map = new HashMap<>();
if(blogId == null) {
map.put("message","blogId丢失!");
return map;
}
Blog blog = blogService.getBlogByBid(blogId);
if(blog == null) {
map.put("blog","不存在当前blog的文章!");
return map;
}
return blog;
}
@RequestMapping("/update")
public Object Update(Integer blogId, @RequestBody Blog blog, @SessionAttribute(value = "user",required = false)User user) {
HashMap<String, Object> map = new HashMap<>();
if(blogId == null) {
map.put("message","blogId丢失!");
return map;
}
blog.setBlogId(blogId);
blog.setUserId(user.getUserId());
blogService.updateBlog(blog);
return map;
}
}
复制代码
17.2 实现前端代码
$.ajax({
url: "updateLoad"+location.search,
method: "GET",
success: function(data,status) {
if(data.message == null) {
let title = document.querySelector('.title');
title.value=data.title;
let content = document.querySelector('.content');
content.value=data.content;
}else{
alert(data.message);
location.assign('home.html');
}
}
})
// 初始化编辑器
var editor = editormd("editor", {
// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
width: "100%",
// 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
height: "calc(100% - 60px)",
// 指定 editor.md 依赖的插件路径
path: "editor.md/lib/",
// 放到 textarea中
saveHTMLToTextArea: true
});
let submit = document.querySelector('.publish');
submit.onclick = function() {
let title = document.querySelector('.title');
let content = document.querySelector('.content');
if(title.value.trim() == ""){
alert('当前文章标题为空,请输入!');
title.focus();
return;
}
if(content.value.trim() == ""){
alert('当前文章内容为空,请输入!');
content.focus();
return;
}
$.ajax({
url: "update"+location.search,
method: "POST",
data: JSON.stringify({title: title.value.trim(), content: content.value.trim()}),
contentType: "application/json;charset=utf-8",
success: function(data,status) {
if(data.message == null){
location.assign('home.html');
}else{
alert(data.message);
location.assign('home.html');
}
}
})
}
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4