SpringBoot 集成MyBatis 集成quratz

SpringBoot集成MyBatis

项目中我集成了AOP 自己封装了一套CRUD 操作 和统一异常处理等等,其实都挺简单的 想要的朋友请留言 在这里我就不贴了

首先需要搭建一个SpringBoot 项目,然后在集成MyBatis 好了 步入正题
pom 文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zhangheng</groupId>
    <artifactId>springBootDemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBootDemo</name>
    <description>SpringBootDemo</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- 引入web项目依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- jpa 依赖 暂时没用到 -->
        <!-- <dependency> -->
        <!-- <groupId>org.springframework.boot</groupId> -->
        <!-- <artifactId>spring-boot-starter-data-jpa</artifactId> -->
        <!-- </dependency> -->
        <!-- mybatis 依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!-- mybatis的分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>4.1.6</version>
        </dependency>
        <!-- 引入Aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- actuator 监控 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- 权限控制 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- devtools 开发效率快 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.0</version>
        </dependency>
        <!-- thymeleaf 渲染引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- 添加对quartz的支持 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
        <!-- 发邮件依赖jar -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

上面的配置其实挺简单的 基本都有说明 如果有不明白的小伙伴请留言 下面是我程序的入口:

package com.zhangheng;

import java.util.Properties;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.github.pagehelper.PageHelper;

/**
 * 程序启动入口
 * 
 * @author zhangh
 * @date 2018年4月26日上午9:16:27
 */
@SpringBootApplication
@EnableAspectJAutoProxy
@MapperScan("com.zhangheng.dao")
public class Application {

    /**
     * @author zhangh
     * @date 2018年4月28日下午5:18:38
     * @param args
     */
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        // 下面的代码可以自定义banner 还可以控制banner的显示方式(log console off)
        // SpringApplication app = new SpringApplication(Application.class);
        // app.setBannerMode(Banner.Mode.OFF);
        // app.run(args);
    }

    @Bean
    public PageHelper pageHelper() {
        PageHelper pageHelper = new PageHelper();
        Properties properties = new Properties();
        properties.setProperty("rowBoundsWithCount", "true");
        properties.setProperty("reasonable", "true");
        properties.setProperty("dialect", "mysql");
        pageHelper.setProperties(properties);
        return pageHelper;
    }
}

下面是我的包结构:
源码结构
资源

接下来讲讲配置文件application.yml:
这是程序的主配置文件入口 推荐使用yml格式 内容如下:

## 主配置文件 这里存放的是公共的配置信息
# 激活对应的环境的配置文件
spring:
 profiles:
  active: dev

  # 模板引擎为html 位置在templates下
 mvc:
  view:
   prefix: classpath:/templates
   suffix: .html

application-dev.yml 的内容如下:这里我集成了许多Spring 模块 如Actuator 监控,security 安全等 具体大家去看吧 还是那句话 不懂的请留言 我尽量第一时间回复

# 服务器的端口和contextPath的配置
server:
 port: 10086
 contextPath: /springboot

# Actuator 监控访问端口 默认值跟上面的端口一样
management:
 port: 10087
 context-path: /manage

spring:
 datasource:  # 数据库的配置 用的是阿里的druid连接池
  url: jdbc:mysql://localhost:3306/mytest
  username: root
  password: 111111
  driver-class-name: com.mysql.jdbc.Driver
  type: com.alibaba.druid.pool.DruidDataSource
  initialSize: 5
  minIdle: 5
  maxActive: 20
  maxWait: 60000
  timeBetweenEvictionRunsMillis: 60000
  minEvictableIdleTimeMillis: 300000
  validationQuery: SELECT 1 FROM DUAL
  testWhileIdle: true
  testOnBorrow: false
  testOnReturn: false
  poolPreparedStatements: true
  maxPoolPreparedStatementPerConnectionSize: 20
  filters: stat,wall,log4j
  connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
 devtools: # 热部署机制 需要依赖一个jar
  restart:
   enabled: true
 mail:
  host: smtp.163.com
  username: zhsj201314@163.com
  password: your password
# MyBatis 的配置
mybatis:
 typeAliasesPackage: com.zhangheng.entity
 mapperLocations: classpath:mapper/*.xml

# 日志管理
logging:
 level:
  com:
   zhangheng:
    dao: debug
 file:
  logs/spring-boot-logging.log

# 权限验证
security:
 user:
  name: iflytek2018
  password: iflytek2018

# endpoints 介绍
endpoints:
 shutdown:
  enabled: false  #true 强制下线
 info: ##自定义info的id
  id: myAppInfo


info:
 app:
  name: springBootDemo
  version: v0.0.1
  author: [email protected]

author:
 name: zhangheng
 email: [email protected]
 age: 18
 neck: 永远18岁

说点自己的心得吧
软件行业 相信大家第一感受就是钱多 如果你觉得你的付出和汇报不成比例 难免会不定期的跳槽,那么问题来了 怎么更好的面试呢? 我个人觉得 养成一个良好的编程习惯非常重要。下面我就说说自己的编程习惯:
1 我对代码的认知非常强,一个单词就应该要表达出它的意思。项目中不要随意的命名;
2 多学习java 的多种设计模式,我不推荐大家去死记硬背,要结合项目灵活的使用;
3 要时刻告诉自己一个方法不能超过20行,如果超过了该怎么办;
4 项目中尽量不要出现一些莫名其妙的数字或者字符串,尽量放到一个常量类或者是枚举类中,不过我推荐大家用枚举,因为枚举本身是一个类 类就有方法 我们还可以操作它们,从而更灵活。
5 要有点责任心,完成自己的任务然后在离职;
6 每天设定一个计划 定期去总结,哪怕今天的计划没实现都没关系 因为设定一个计划是让自己始终保持一个去学习的动力。

Spring Boot 集成quratz

由于上面我们的pom文件已经加入了对应的maven坐标 所以下面我们直接配置quratz,在这里我说一点 spring默认是不管理quratz 中的bean 的 ,因为它把权力交给了quratz,所以在job中注入spring中的bean是不生效了 这也是大家的关系点。有许多中方法可以解决 下面我将用一种简单的方法。

quartz.properties 内容 其实没啥难度 不懂的可以去官网查 如果还不懂请留言

## 如果是集群部署 该值设置成true 同时instanceId设置成AUTO 因为要保证集群中的每个实例都是唯一的
#org.quartz.jobStore.isClustered=true
#org.quartz.scheduler.instanceId=AUTO
#org.quartz.scheduler.instanceId=mySchedulerId
org.quartz.scheduler.rmi.export=false
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false

org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=15
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=false

org.quartz.jobStore.misfireThreshold=5000
# 存储在内存中
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#定时任务存储在数据库中
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.dataSource=qzDS
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate

org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/quartz?useUnicode=true&characterEncoding=UTF-8
org.quartz.dataSource.qzDS.user=root
org.quartz.dataSource.qzDS.password=111111
org.quartz.dataSource.qzDS.maxConnections=10

下面是我的发邮箱job类
`package com.iflytek.ifi.quartz.myjob;

import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.iflytek.ifi.quartz.entity.MyJob;
import com.iflytek.ifi.quartz.entity.User;
import com.iflytek.ifi.quartz.service.impl.UserServiceImpl;
import com.iflytek.ifi.quartz.util.SendMailUtil;

/**
* 发邮件job
*
* @author zhangh
* @date 2018年5月7日下午3:46:19
*/
@Component(“sendEmailJob”)
public class SendEmailJob extends MyJob implements Job {
private static Logger logger = Logger.getLogger(SendEmailJob.class);

@Autowired
private SendMailUtil sendMailUtil;

@Autowired
private UserServiceImpl userService;

/**
 * 发邮件的具体内容
 * 
 */
@Override
public void execute(JobExecutionContext context) {
    JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
    String email = jobDataMap.getString("email");
    String emailType = jobDataMap.getString("emailType");// 1 普通邮件 2模板邮件 默认是普通邮件
    String phone = jobDataMap.getString("phone");
    logger.info(context.getJobDetail().getKey().getGroup()+ phone + context.getJobDetail().getDescription());
    if("2".equals(emailType)){
        try {
            sendMailUtil.sendTemplateMail(email, "模板邮件", jobDataMap);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }else{
        sendMailUtil.sendMail(email, "审核邮件", "恭喜您审核通过");
    }
    userService.add(new User(phone));
}
public SendEmailJob() {}

}`

job 处理类
`package com.iflytek.ifi.quartz.service.impl;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.iflytek.ifi.quartz.entity.JobEntity;
import com.iflytek.ifi.quartz.service.JobService;

@Service(“jobService”)
public class JobServiceImpl implements JobService {

@Autowired
@Qualifier("Scheduler")
private Scheduler scheduler;

@Value("${myJob.alias.dir}")
private String dir;

@Override
public void addJob(JobEntity jobEntity) throws Exception {

    // 启动调度器
    // scheduler.start();
    // 构建job信息
    JobDetail jobDetail = JobBuilder.newJob(getClass(jobEntity.getJobClassName()).getClass())
                                    .withIdentity(jobEntity.getJobClassName(), jobEntity.getJobGroupName())
                                    .withDescription(jobEntity.getJobDescription())
                                    .storeDurably(jobEntity.getDurabe())
                                    .usingJobData(jobEntity.getJobDataMap())
                                    .build();
    // 表达式调度构建器(即任务执行的时间)
    CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(jobEntity.getCronExpression());
    // 按新的cronExpression表达式构建一个新的trigger
    CronTrigger trigger = TriggerBuilder.newTrigger()
                                        .withIdentity(jobEntity.getJobClassName(), jobEntity.getJobGroupName())
                                        .withSchedule(scheduleBuilder)
                                        .build();
    scheduler.scheduleJob(jobDetail, trigger);
    if(!jobEntity.getStartWork()){          
        scheduler.pauseJob(JobKey.jobKey(jobEntity.getJobClassName(), jobEntity.getJobGroupName()));
    }
}

private Job getClass(String classname) throws Exception {
    Class<?> class1 = Class.forName(dir+classname.substring(0, 1).toUpperCase()+classname.substring(1));
    return (Job) class1.newInstance();
}

@Override
public void deleteJob(String name,String group) throws SchedulerException {
    scheduler.deleteJob(JobKey.jobKey(name, group));
}

@Override
public void startJob() throws SchedulerException {
    if(!scheduler.isStarted()){
        scheduler.start();
    }       
}

@Override
public void startDelayed(int seconds) throws SchedulerException {
    scheduler.startDelayed(seconds);
}

@Override
public void shutdown() throws SchedulerException {
    if(!scheduler.isShutdown()){
        scheduler.shutdown();
    }
}

@Override
public void shutdown(boolean waitForJobsToComplete) throws SchedulerException {
    scheduler.shutdown(waitForJobsToComplete);
}

@Override
public void pauseJob(String name,String group) throws SchedulerException {
    scheduler.pauseJob(JobKey.jobKey(name, group));
}

@Override
public void pauseTrigger(String name,String group) throws SchedulerException {
    scheduler.pauseTrigger(TriggerKey.triggerKey(name, group));
}

@Override
public void resumeJob(String name,String group) throws SchedulerException {
    scheduler.resumeJob(JobKey.jobKey(name, group));
}

@Override
public void resumeTrigger(String name,String group) throws SchedulerException {
    scheduler.resumeTrigger(TriggerKey.triggerKey(name, group));
}

@Override
public void pauseAll() throws SchedulerException {
    scheduler.pauseAll();
}

@Override
public void resumeAll() throws SchedulerException {
    scheduler.resumeAll();
}

}
`

package com.iflytek.ifi.quartz.service;

import org.quartz.SchedulerException;

import com.iflytek.ifi.quartz.entity.JobEntity;

public interface JobService {

    /**
     * 添加任务
     * 
     * @author zhangh
     * @date 2018年5月8日上午8:40:46
     * @param jobEntity
     * @throws Exception
     */
    public void addJob(JobEntity jobEntity) throws Exception;

    /**
     * 根据任务名称和分组删除任务
     * 
     * @author zhangh
     * @date 2018年5月8日上午8:40:59
     * @param name
     * @param group
     * @throws SchedulerException
     */
    public void deleteJob(String name, String group) throws SchedulerException;

    /**
     * 开始所有任务
     * 
     * @author zhangh
     * @date 2018年5月8日上午8:41:21
     */
    public void startJob() throws SchedulerException;

    /**
     * 延迟seconds 秒后启动所有任务
     * 
     * @author zhangh
     * @date 2018年5月8日上午8:44:44
     * @param seconds
     *            秒
     */
    public void startDelayed(int seconds) throws SchedulerException;

    /**
     * 关闭所有任务
     * 
     * @author zhangh
     * @date 2018年5月8日上午8:53:39
     * @throws SchedulerException
     */
    public void shutdown() throws SchedulerException;

    /**
     * 关闭所有任务时 是否等所有的任务执行完毕才关闭
     * 
     * @author zhangh
     * @date 2018年5月8日上午8:54:25
     * @param waitForJobsToComplete
     * @throws SchedulerException
     */
    public void shutdown(boolean waitForJobsToComplete) throws SchedulerException;

    /**
     * 暂停任务
     * @author zhangh
     * @date 2018年5月8日下午1:48:24
     * @param jobKey
     * @throws SchedulerException
     */
    public void pauseJob(String name,String group) throws SchedulerException;

    /**
     * 暂停触发器
     * @author zhangh
     * @date 2018年5月8日下午1:58:02
     * @param triggerKey
     * @throws SchedulerException
     */
    public void pauseTrigger(String name,String group) throws SchedulerException;

    /**
     * 唤醒任务
     * @author zhangh
     * @date 2018年5月8日下午1:58:13
     * @param jobKey
     * @throws SchedulerException
     */
    public void resumeJob(String name,String group) throws SchedulerException;

    /**
     * 唤醒触发器
     * @author zhangh
     * @date 2018年5月8日下午1:58:25
     * @param triggerKey
     * @throws SchedulerException
     */
    public void resumeTrigger(String name,String group) throws SchedulerException;

    /**
     * 暂停所有任务
     * @author zhangh
     * @date 2018年5月8日下午1:58:35
     * @throws SchedulerException
     */
    public void pauseAll() throws SchedulerException;


    /**
     * 唤醒所有任务
     * @author zhangh
     * @date 2018年5月8日下午1:58:44
     * @throws SchedulerException
     */
    public void resumeAll() throws SchedulerException;
}
package com.iflytek.ifi.quartz.util;

import java.io.File;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import freemarker.template.Template;

/**
 * 邮件发送工具类
 * 
 * @author zhangh
 * @date 2018年5月10日下午3:00:32
 */
@Component
public class SendMailUtil {

    @Value("${spring.mail.username}")
    private String emailFrom;

    @Autowired
    private FreeMarkerConfigurer freeMarkerConfigurer; // 自动注入

    @Autowired
    private JavaMailSender mailSender;

    /**
     * 发送普通邮件
     * 
     * @author zhangh
     * @date 2018年5月10日下午2:53:03
     * @param sendTo
     * @param subject
     * @param content
     */
    public void sendMail(String sendTo, String subject, String content) {

        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(emailFrom);
        message.setTo(sendTo);
        message.setSubject(subject);
        message.setText(content);
        mailSender.send(message);
    }

    /**
     * 发送html邮件
     * 
     * @author zhangh
     * @date 2018年5月10日下午2:58:53
     * @param sendTo
     * @param subject
     * @throws MessagingException
     */
    public void sendHtmlMail(String sendTo, String subject, String htmlContent) throws MessagingException {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
        mimeMessageHelper.setFrom(emailFrom);
        mimeMessageHelper.setTo(sendTo);
        mimeMessageHelper.setSubject(subject);
        mimeMessageHelper.setText(htmlContent, true);
        mailSender.send(mimeMessage);
    }

    /**
     * 发送带附件的邮件
     * 
     * @author zhangh
     * @date 2018年5月10日下午3:09:45
     * @param sendTo
     * @param subject
     * @param content
     * @param file
     * @throws MessagingException
     */
    public void sendAttachmentMail(String sendTo, String subject, String content, File file) throws MessagingException {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
        mimeMessageHelper.setFrom(emailFrom);
        mimeMessageHelper.setTo(sendTo);
        mimeMessageHelper.setSubject(subject);
        mimeMessageHelper.setText(content);
        mimeMessageHelper.addAttachment("image.jpg", file);
        mailSender.send(mimeMessage);
    }

    /**
     * 发送有静态资源的邮件
     * 
     * @author zhangh
     * @date 2018年5月10日下午3:26:48
     * @param sendTo
     * @param subject
     * @param content
     * @param file
     * @throws MessagingException
     */
    public void sendInlineMail(String sendTo, String subject, String content, File file) throws MessagingException {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
        mimeMessageHelper.setFrom(emailFrom);
        mimeMessageHelper.setTo(sendTo);
        mimeMessageHelper.setSubject(subject);
        mimeMessageHelper.setText("<html><body>带静态资源的邮件内容 图片:<img src='cid:picture' /></body></html>", true);
        mimeMessageHelper.addInline("picture", file);
        mailSender.send(mimeMessage);
    }

    /**
     * 发送模板邮件
     * @author zhangh
     * @date 2018年5月11日上午10:23:11
     * @param sendTo
     * @param subject
     * @param model
     * @throws Exception
     */
    public void sendTemplateMail(String sendTo, String subject,Object model) throws Exception {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
        mimeMessageHelper.setFrom(emailFrom);
        mimeMessageHelper.setTo(sendTo);
        mimeMessageHelper.setSubject(subject);
        Template template = freeMarkerConfigurer.getConfiguration().getTemplate("mail.ftl");
        String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
        mimeMessageHelper.setText(html, true);
        mailSender.send(mimeMessage);
    }

    public SendMailUtil() {
    }
}

代码太多了 还是整个git仓库吧

https://github.com/853827276/springBootQuratz
https://github.com/853827276/springBoot

大家下载去看吧 纯手写 累死了

猜你喜欢

转载自blog.csdn.net/forever_insist/article/details/80281052