瑞吉外卖项目详细分析笔记及所有功能补充代码

Posted 随身携带的笑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了瑞吉外卖项目详细分析笔记及所有功能补充代码相关的知识,希望对你有一定的参考价值。

目录

项目刨析简介

#2022年末了,记录一下学习的项目实战经验和笔记吧
这个是瑞吉外卖项目,补充一些视频里面没有定义的功能和记录一些功能实现逻辑的笔记;仅供学习参考,本人代码可能不太规范,也有可能自己写了有些错误自己没有察觉,但是功能自己测试是没有问题的;感谢各位的阅览,如有问题欢迎指正,如有遗漏后续继续补充


技术栈

涉及到的技术有Spring,Springboot,Mybatis-plus,mysql,Redis,Linux,Git,Spring Cache,Sharding-JDBC,nginx,Swagger。(Apifox这些工具应该不算技术吧,用的工具就不列举了)


项目介绍

该项目是一个外卖点餐系统,它分为后台管理端和用户移动端两方面开发,后台管理端为商家提供管理菜品套餐的服务,移动端为用户提供点菜下单功能。最终通过git管理项目,并用nginx部署前端,tomcat部署后端,使用mysql主从复制,从库读取,主库写入,再用shell脚本部署到服务器上。


项目源码

项目码云地址:https://gitee.com/dkgk8/reggie-git


一.架构搭建

1.初始化项目结构

新建一个springboot项目
pom导入的坐标

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>com.github.xiaoymin</groupId>
			<artifactId>knife4j-spring-boot-starter</artifactId>
			<version>3.0.2</version>
		</dependency>

<!--		<dependency>-->
<!--			<groupId>org.apache.shardingsphere</groupId>-->
<!--			<artifactId>sharding-jdbc-spring-boot-starter</artifactId>-->
<!--			<version>4.1.1</version>-->
<!--		</dependency>-->

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<scope>compile</scope>
		</dependency>

		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.4.2</version>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.20</version>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.76</version>
		</dependency>

		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>2.6</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.23</version>
		</dependency>

		<dependency>
			<groupId>com.aliyun</groupId>
			<artifactId>aliyun-java-sdk-core</artifactId>
			<version>4.5.16</version>
		</dependency>
		<dependency>
			<groupId>com.aliyun</groupId>
			<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
			<version>2.1.0</version>
		</dependency>

yml配置文件添加的信息

server:
  port: 8080
spring:
#  application:
#    name: reggie_take_out
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: root
      password: 123456
  redis:
    host: localhost
    port: 6379
    database: 0
  cache:
    redis:
      time-to-live: 1800000  #ms ->30min

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
reggie:
  path: D:\\SpringBoot_Reggie\\reggie_take_out\\src\\main\\resources\\static\\front\\hello\\

我后面将项目运行在服务器上了所以用了多环境开发,本地跑的不用在意这步

项目大致结构如下

感觉使用mybatis-plus之后就是
实体类->mapper->service->serviceImpl->controller
这个步骤写程序了

2.数据库表结构设计



不每个表展示了,这里拿典型的员工表来看

3.项目基本配置信息添加

导入前端资源
在默认页面和前台页面的情况下,直接把这俩拖到resource目录下直接访问是访问不到的,因为被mvc框架拦截了,其实用springboot,可以直接放在static目录下,但是仍然不能直接访问前端页面,所以这里也可以直接放行static就好了
所以我们要编写一个映射类放行这些资源
WebMvcConfig类

公共字段的自动填充

这个我在另一篇文章写了很详细,链接:自动填充公共字段

全局异常处理类

虽然遇到异常后可以使用try-catch来处理,但是,代码量一大起来,许多的try catch就会很乱,代码也不简洁,不容易阅读,所以我们使用全局异常处理,在Common包下

自定义异常类

返回结果封装的实体类

为了便于前后端数据传递,使用对象的形式封装数据更合适

@Data
public class R<T> implements Serializable 

    private Integer code; //编码:1成功,0和其它数字为失败

    private String msg; //错误信息

    private T data; //数据

    private Map map = new HashMap(); //动态数据

    public static <T> R<T> success(T object) 
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    

    public static <T> R<T> error(String msg) 
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    

    public R<T> add(String key, Object value) 
        this.map.put(key, value);
        return this;
    


二.管理端业务开发

1.员工管理相关业务

1.1员工登录

登录逻辑如下

    @PostMapping("/login")
    public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee)
        //1.将页面提交的明文密码进行md5加密
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        //2.根据页面提交的用户名username查数据库
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper);
        //3.如果没有查询到则返回登录失败结果
        if (emp == null)
            return R.error("登录失败");
        
        //4.密码比对,如果不一致则返回登录失败结果
        if (!emp.getPassword().equals(password))
            return R.error("登录失败");
        
        //5.查看员工账号状态是否锁定,若是禁用状态返回禁用信息
        if (emp.getStatus() == 0)
            return R.error("账号异常,已锁定");
        
        //6.登录成功,将员工id存入Session  并返回登录成功结果
        request.getSession().setAttribute("employee",emp.getId());
        return R.success(emp);
    

1.2员工退出

就是清除员工登录时存入session的员工id

    @PostMapping("/logout")
    public R<String> logout(HttpServletRequest request)
        //1.清理Session中保存的当前登录员工id
        request.getSession().removeAttribute("employee");
        return R.success("退出成功");
    

1.3过滤器拦截

现在没有过滤器,用户直接不用登录通过url+资源名可以随便访问,所以要加个过滤器,没有登陆时,拦截请求,不给访问,自动跳转到登陆页面
过滤器处理逻辑

在启动类上添加注解@ServletComponentScan
过滤器配置类注解@WebFilter(filterName=“拦截器类名首字母小写”,urlPartten=“要拦截的路径,比如/*”)

判断用户是否已经登录,之前因为存入session里面有一个名为employee的对象,里面放的时用户id,那么只需要用getAttribute,看看session里get的数据是否为null就知道他是否在登陆状态

这里提一嘴
调用Spring核心包的字符串匹配类的对象,对路径进行匹配,并且返回比较结果
如果相等就为true

public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();


直接上代码

/**
 * 检查用户是否登录的过滤器
 */

@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter 
    //路径匹配器,支持通配符
    public static final AntPathMatcher PATH_MATCHER =new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException 

        HttpServletRequest request=(HttpServletRequest) servletRequest;
        HttpServletResponse response=(HttpServletResponse) servletResponse;

        //1.获取本次请求uri
        String requestURI = request.getRequestURI();
        //定义不需要处理的请求路径
        String[] urls=new String[]
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**",
                "/common/**",
                "/user/sendMsg",
                "/user/login",
                "/doc.html",
                "/webjars/**",
                "/swagger-resources",
                "/v2/api-docs"
        ;
        //2.判断本次请求是否需要处理
        boolean check = check(urls, requestURI);
        //3.如果不需要处理则直接放行
        if (check)
            filterChain.doFilter(request,response);
            return;
        
        //4-1.判断登录状态,如果已经登录,则直接放行
        if (request.getSession().getAttribute("employee")!=null)
            Long empId = (Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(empId);
            filterChain.doFilter(request,response);
            return;
        
        //4-2.判断移动端登录状态,如果已经登录,则直接放行
        if (request.getSession().getAttribute("user")!=null)
            Long userId = (Long) request.getSession().getAttribute("user");
            BaseContext.setCurrentId(userId);
            filterChain.doFilter(request,response);
            return;
        
        //5如果未登录则,通过输出流方式向客户端页面响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;

    

    /**
     * 路径匹配,检查本次请求是否需要放行
     */
    public boolean check(String[] urls,String requestURI)
        //遍历的同时调用PATH_MATCHER来对路径进行匹配
        for (String url : urls)
            boolean match = PATH_MATCHER.match(url,requestURI);
            if (match)
                //匹配到了可以放行的路径,直接放行
                return true;
            
        
        return false;
    

1.4员工信息修改

员工状态修改

遇到了问题,数据库id根据雪花算法有19位,而js对Long型数据处理时会丢失精度,只能保证前16位
解决办法: 服务端给页面响应json数据时,将Long型数据统一转为String字符串


将Long型的Id转换为String类型的数据

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper 

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() 
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, 
 

前言

  • 本项目《听海餐厅》是基于黑马《瑞吉外卖》改名而来,感谢黑马贡献的高质量视频教程。
  • 本项目将短信登录改造成了邮箱登录。只想看邮箱验证码登录的小伙伴点此跳转【邮箱验证码】
  • 为了避免各位小伙伴面试时被面试官嘲讽【你们项目组还挺大啊】的尴尬场面,将原项目改名成了《听海餐厅》。

本期目录


一、 软件开发整体介绍

  • 本项目是一个单体架构,没有使用微服务。

1. 软件开发流程


2. 角色分工

  • 项目经理:对整个项目负责,任务分配、把控进度。
  • 产品经理:进行需求调研,输出需求调研文档、产品原型等。
  • UI设计师:根据产品原型输出界面效果图。
  • 架构师:项目整体架构设计、技术选型等。
  • 开发工程师:代码实现。
  • 测试工程师:编写测试用例,输出测试报告。
  • 运维工程师:软件环境搭建、项目上线。

3. 软件环境

  • 开发环境 (development):开发人员在开发阶段使用的环境,一般外部用户无法访问。
  • 测试环境 (testing):专门给测试人员使用的环境,用于测试项目,一般外部用户无法访问。
  • 生产环境 (production):即线上环境,正式提供对外服务的环境。

二、 项目介绍


1. 项目介绍

1.1 B端和C端

  • 本项目 (瑞吉外卖) 是专门为餐饮企业 (餐厅、饭店) 定制的一款软件产品,包括系统管理后台和移动端应用两部分。
    • 其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。
    • 移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等。

1.2 开发周期

  • 本项目共分为3期进行开发:
  • 第一期主要实现基本需求,其中移动端应用通过 H5 实现,用户可以通过手机浏览器访问。
  • 第二期主要针对移动端应用进行改进,使用微信小程序实现,用户使用起来更加方便。
  • 第三期主要针对系统进行优化升级,提高系统的访问性能。

2. 产品原型展示


2.1 后台管理登录界面



2.2 后台管理界面展示


2.3 用户端登录界面展示



2.4 邮件验证码展示



2.5 用户端界面展示


3. 技术选型


4. 功能架构


5. 角色

  • 后台系统管理员:登录后台管理系统,拥有后台系统中的所有操作权限。
  • 后台系统普通员工:登录后台管理系统,对菜品、套餐、订单等进行管理。
  • C端用户:登录移动端应用,可以浏览菜品、添加购物车、设置地址、在线下单等。

三、 开发环境搭建

1. 数据库环境搭建

1.1 数据库设计

  • 本项目不涉及到微服务架构,所有模块的数据表都统一放在一个数据库中。

  • 因此只需创建一个数据库,命名为 reggie

    CREATE DATABASE IF NOT EXISTS reggie CHARACTER SET 'utf8';
    

1.2 数据表设计

  • 根据功能划分为不同模块,我根据模块的不同创建不同的数据表。

    数据表描述难点
    后台系统用户表该数据库的root用户就是管理员
    C 端用户表
    订单表
    菜品表
    地址表
    套餐表
  • 很不幸,我想破脑袋也只能想到划分为 6 张表。但老师对该项目却划分出 11 张表。

  • 导入老师设计好的数据表:

  • 老师设计数据表的思路:

    数据表描述
    address_book地址簿表
    category菜品和套餐的分类表 (荤菜、素菜、周一套餐 )
    dish菜品表
    dish_flavor菜品口味关系表
    employee后台系统普通员工表
    order_detail订单明细表
    orders订单表
    setmeal套餐表
    setmeal_dish套餐菜品关系表
    shopping_cart购物车表 (感觉增删会很频繁)
    userC 端用户表

2. Spring Boot项目搭建

  • 本项目使用的 IDEA 版本为 2022.2.2 Ultimate 。

2.1 检查Maven目录与本地仓库

  • 创建前首先确保自己的 Maven 软件和 Maven 仓库已经与 IDEA 关联好:

2.2 创建Spring Boot项目

  • 按下图设置:

  • 导入依赖,有些依赖如 MyBatis-Plus 和 Druid 依赖没有被官方收录,我们先导入下图的三个:

2.3 整合第三方依赖

  • 打开项目的 pom.xml 文件,加入以下依赖:

    <!-- MyBatis-Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.3</version>
    </dependency>
    
    <!-- Lombok官方收录无需写版本号 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    
    <!-- Fastjson:将对象转成json -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.76</version>
    </dependency>
    
    <!-- 通用语言包 -->
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
    </dependency>
    
    <!-- Druid数据库连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.12</version>
    </dependency>
    
  • 牢记每次修改完 pom.xml 配置文件都必须按 Shift + Ctrl + O 来刷新 Maven,Maven 才能帮你下载并导入依赖。

2.4 编写Spring Boot配置文件

  • 把 Spring Boot 默认的配置文件 application.properties 修改为 application.yml

  • application.yml 中添加如下配置:

    # 配置服务器端口
    server:
      port: 8080
    
    # 配置Druid数据库连接池
    spring:
      application:
        # 应用名称 (可选)
        name: reggie_take_out
      datasource:
        druid:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
          username: root
          password: 你的数据库密码
    
    # 配置Mybatis-Plus
    mybatis-plus:
      configuration:
        # 在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
        # 例如:属性名映射 user_name --> userName
        # 例如:类名映射 address_book --> AddressBook
        map-underscore-to-camel-case: true
         # 开启MP运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      global-config:
        db-config:
          id-type: ASSIGN_ID
    

2.5 运行Spring Boot启动类

  • 打开 src/main/java/edu/ouc/ReggieTakeOutApplication.java ,添加 Lombok 提供的 @Slf4j 注解,方便输出日志来调试:

    @Slf4j  // 日志
    @SpringBootApplication  // Spring Boot启动类
    public class ReggieTakeOutApplication 
    
        public static void main(String[] args) 
            SpringApplication.run(ReggieTakeOutApplication.class, args);
            // 打印Slf4j日志
            log.info("项目启动成功");
        
    
    
  • 运行 ReggieTakeOutApplication.java

  • 看到如图所示的输出,就说明你的 Spring Boot 开发环境已经搭建好了。

2.6 导入前端代码

  • 本项目以后端开发为主要学习目标,前端代码已经提供好,直接加载即可。

  • 粘贴到 src/main/resources/static 目录下:

  • 重启项目,在浏览器中输入 http://localhost:8080/backend/index.html :

  • 看到此页面说明前端代码已成功导入。

四、 后台登录退出功能开发

1. 后台登录功能开发

  • 本章从后台管理页面的登录功能开始。

1.1 需求分析

① 请求分析

  • 先来查看后台登录的前端页面,浏览器地址栏输入 http://localhost:8080/backend/page/login/login.html

  • 按 F12 打开浏览器的控制台,点击 “登录” 按钮,查看浏览器是以何种方式向服务器发送请求的:

  • 可以看到请求方式是 POST ,请求 URL 是 http://localhost:8080/employee/login 。由于我们还没写对应的 Controller ,报 404 是很正常的。

  • 按照 Spring Boot 的开发思路,我们应该按照:数据层 (Mapper) –> 服务层 (Service) –> 表现层 (Controller) 三步走来开发。

② 数据库分析

  • 后台登录功能对应的数据表为 employee ,其表结构如下:

    DESC employee;
    

  • 对数据表中的字段进行逐一分析:

    字段作用
    id员工编号,主键,为什么不用自增?可能使用了MyBatis-Plus的雪花算法
    name员工姓名
    username登录账号,加了唯一约束,登录账号不允许重复
    password登录密码
    phone手机号码
    sex性别
    id_number身份证号码
    status员工状态 (禁用/可用)
    create_time创建时间
    update_time修改时间
    create_user创建人是谁,以员工ID记录
    update_user修改人是谁,以员工ID记录
  • 目前数据表 employee 仅有一条记录,就是后台管理员的记录:

③ 前端代码分析

  • 打开 src/main/resources/static/backend/page/login/login.html ,这是后台管理系统的登录页面。

  • 其中,我们后端工程师需要关心的最核心的东西就是:前后端数据交换的统一格式 (也称前后端协议) 。具体来说,是后端响应给前端的数据格式。就是下面这段代码:

    methods: 
        async handleLogin() 
            this.$refs.loginForm.validate(async (valid) => 
                if (valid) 
                    this.loading = true
                    let res = await loginApi(this.loginForm)
                    if (String(res.code) === '1')  // code:状态,1表示登录成功
                        localStorage.setItem('userInfo', JSON.stringify(res.data))	// data:数据,这里指的是账号和密码
                        window.location.href = '/backend/index.html'
                     else 	//登录失败
                        this.$message.error(res.msg)	// msg:登录失败提示信息
                        this.loading = false
                    
                
            )
        
    
    
  • 从上面这段登录代码中可以看出来,这里和前端工程师约定好的前后端数据交换的统一格式应当包含 3 部分:

    协议描述
    code状态,1 表示成功
    data传递的数据
    msg操作失败/成功的提示信息

    json 格式如下所示:

    res
        "code":1,
        "data":
        	"username":"admin",
            "password":"123456"
    	,
        "msg":"登录成功/登录失败"
    
    

2. 代码编写

2.1 创建实体类

  • 创建数据表 employee 的实体类 Employee.java 。并用 Lombok 快速生成 Getter 、Setter 、toString() 、equals() 等。

  • 其中的属性与数据表 employee 的字段一一对应。

  • 创建 src/main/java/edu/ouc/entity/Employee.java

    @Data
    public class Employee 
        private static final Long serialVersionUID = 1L;// 序列化ID
        private Long id;
        private String name;
        private String username;
        private String password;
        private String phone;
        private String sex;
        private String idNumber;
        private Integer status;
        private LocalDateTime createTime;
        private LocalDateTime updateTime;
        
        @TableField(fill = FieldFill.INSERT)
        private Long createUser;
        
        @TableField(fill = FieldFill.INSERT)
        private Long updateUser;
    
    

2.2 数据层开发

  • 登录只涉及到对数据库的查询操作。即,根据账户名 username 查询密码 password

  • 创建数据层、服务层和表现层对应的文件夹。

① 创建数据层接口
  • 创建 src/main/java/edu/ouc/mapper/EmployeeMapper.java

  • 继承 MyBatis-Plus 的 BaseMapper<T> 泛型接口,添加 @Mapper 注解。就能获取父类 BaseMapper<T> 中已经写好的增删改查方法。

    @Mapper
    public interface EmployeeMapper extends BaseMapper<Employee> 
    
    

2.3 服务层开发

① 创建服务层接口
  • 创建 src/main/java/edu/ouc/service/IEmployeeService.java

  • 我们可以让服务层接口继承 MyBatis-Plus 的 IService<T> 泛型接口来进行快速开发。

    public interface IEmployeeService extends IService<Employee> 
    
    
  • 继承 IService<T> 后能获取很多通用的增删改查方法:

②创建服务层接口实现类
  • 创建 src/main/java/edu/ouc/service/impl/IEmployeeServiceImpl.java
  • 我们可以让其先继承 MyBatis-Plus 的 ServiceImpl<M,T> ,再实现 IEmployeeService.java 。其中,<M,T> 中的 M 指的是对应的 DAO 接口,T 指的是实体类。这样,我们就无需实现 IEmployeeService.java 中全部的方法,而是根据需要,既可以使用提供的基础 CRUD 方法,也可以自定义新的方法。

2.4 表现层开发

① 创建前后端统一格式协议类R

(c)2006-2024 SYSTEM All Rights Reserved IT常识