首页 > 其他分享 >瑞吉外卖实战项目全攻略

瑞吉外卖实战项目全攻略

时间:2022-10-22 11:35:56浏览次数:49  
标签:org springframework 全攻略 瑞吉 外卖 import reggie qiuluo com

该系列将记录一份完整的实战项目的完成过程,该篇属于第四天

案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容

该篇我们将完成以下内容:

  • 文件上传下载
  • 新增菜品
  • 菜品信息分页查询
  • 修改菜品

文件上传下载

由于是第一次接触文件上传下载,我们分为五个小阶段讲解

文件上传介绍

文件上传,也称为upload,是指将本地图片,视频,音频等文件上传到服务器上,可以供其他用户浏览下载的过程

首先我们介绍文件上传对前端的要求:

  • 以POST方法提交数据
  • 采用Multipart格式上传文件
  • 使用input的file空间上传

尽管前端组件库提供了相应的上传组件,但这些组件底层仍旧采用上述要求的格式构造

然后我们介绍文件上传在后端的实现:

  • 通常采用Apache的两个组件:commons-fileupload 和 commons-io

目前我们的Spring框架在Spring-web包下对文件上传进行了封装,简化了服务端代码

我们只需要在Controller中的方法声明一个MultipartFile类型的参数即可接收上传的数据

文件下载介绍

文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程

通过浏览器进行文件下载,通常有两种表现形式:

  • 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
  • 直接在浏览器中打开

通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程

文件上传代码实现

测试用例:

  • 资料中为我们提供了一个文件上传下载的示例页面:upload.html
  • 可以复制在backend/page/demo下,在我们的代码实现过程中进行调试

我们先来思考一下文件上传的逻辑:

  • 我们会获得一个文件File,但这个文件是临时文件,所以我们需要将他储存在电脑中
  • 我们的储存位置需要固定,但是这个文件的名称和后缀名需要重新书写,但还要保证名称随机不会重复,后缀名和原文件相同

接下来我们正式开始文件上传代码的实现:

  1. 书写一个CommonController服务层并构造基本框架:
package com.qiuluo.reggie.controller;

import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.CoyoteOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

/**
* 下载操作
* @param MultipartFile file 是文件上传的唯一必备代码,代表上传的数据
* 注意:file只是一个临时文件,当我们的request请求结束时,file也会消失,所以我们需要将它保存起来
* @return
*/
@PostMapping("/upload")
public Result<String> upload(MultipartFile file){
// 我们可以直接记录日志查看是否收集到file
log.info(file.toString());
}

}
  1. 书写内部代码,阐明书写思路:
package com.qiuluo.reggie.controller;

import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.CoyoteOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

@Value("${reggie.path}")
private String BasePath;

/**
* 下载操作
* @param file 注意需要与前端传来的数据名一致
* @return
*/
@PostMapping("/upload")
public Result<String> upload(MultipartFile file){
// 由于我们的file是临时文件,我们需要将该文件保存在计算机上

try {
// 这个方法可以转载文件到指定目录
file.transferTo(new File("D:/hello.jpg"));
} catch (IOException e) {
e.printStackTrace();
}

// 注意:我们需要返回文件名,因为我们还需要让图片回显,回显就需要再次传入文件名来查询文件
return Result.success(fileName);
}

}
  1. 在yaml配置文件中书写全局变量,使存放路径固定:
# 我们定义了一个reggie.path的参数来存放我们存放文件的地址
reggie:
path: E:\imgs\
  1. 回到主函数中书写代码:
package com.qiuluo.reggie.controller;

import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.CoyoteOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

/*
目前我们的文件存放已经有了固定的位置
但是我们的文件名以及后缀文件名还没有获得,我们将把它们变为动态的
*/

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

// 定义主路径
@Value("${reggie.path}")
private String BasePath;

/**
* 下载操作
* @param file 注意需要与前端传来的数据名一致
* @return
*/
@PostMapping("/upload")
public Result<String> upload(MultipartFile file){
// 注意:file只是一个临时文件,当我们的request请求结束时,file也会消失,所以我们需要将它保存起来

// 这个方法可以获得文件的原名称,但不推荐设置为文件名保存(因为可能出现重复名称导致文件覆盖)
String originalFilename = file.getOriginalFilename();

// 将原始文件的后缀截取下来
String substring = originalFilename.substring(originalFilename.lastIndexOf("."));

// UUID生成随机名称,文件名设置为 UUID随机值+源文件后缀
String fileName = UUID.randomUUID().toString() + substring;

// 判断文件夹是否存在,若不存在需创建一个
File dir = new File(BasePath);

if (!dir.exists()){
dir.mkdirs();
}

// 这个方法可以转载文件到指定目录
try {
file.transferTo(new File(BasePath + fileName));
} catch (IOException e) {
e.printStackTrace();
}
return Result.success(fileName);
}
}

文件下载代码实现

同样我们来思考一下文件下载的逻辑:

  • 首先我们根据前端传来的名字与我们设置的固定路径得到文件
  • 然后我们直接将文件的内容采用输出流的形式输出到前端即可

我们的文件下载同时在CommonController中实现:

  1. 书写主体框架
package com.qiuluo.reggie.controller;

import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.CoyoteOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

@Value("${reggie.path}")
private String BasePath;

/**
*
* @param name
* @param response
* @return
*/
@GetMapping("/download")
public void download(String name, HttpServletResponse response){
// 我们将在里面实现,读取文件,再返回文件的操作
}
}
  1. 实现内部逻辑
package com.qiuluo.reggie.controller;

import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.CoyoteOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {

@Value("${reggie.path}")
private String BasePath;

/**
* 文件下载
* @param name
* @param response
* @return
*/
@GetMapping("/download")
public void download(String name, HttpServletResponse response){

try {
// 输入流获得数据
FileInputStream fileInputStream = new FileInputStream(new File(BasePath + name));

// 输出流写出数据
ServletOutputStream outputStream = response.getOutputStream();

// 设置文件类型(可设可不设)
response.setContentType("image/jpeg");

// 转载数据
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fileInputStream.read(bytes)) != -1){
outputStream.write(bytes,0,len);
outputStream.flush();
}

// 关闭数据
fileInputStream.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

实际测试

我们打开之前转载过来的upload.html页面,传进照片后照片显示在页面中即为成功

新增菜品

我们的功能开发通常分为三部分

需求分析

当我们点击页面的菜品的新添时,会弹出页面,我们根据这个页面进行分析:

瑞吉外卖实战项目全攻略_spring

我们需要注意,这里还有一个菜品分类的下拉框,其中菜品分类的数据是通过请求到后台最后到数据库查询所得到的:

瑞吉外卖实战项目全攻略_数据_02

此外我们点击保存时,还会将一个菜品的相关信息请求返回后台:

瑞吉外卖实战项目全攻略_数据_03

我们需要注意的是:这里返回的并不仅仅只有菜品Dish的数据表,还包括了调料Dish Flavor的数据表

所以我们得到数据时不能采用Dish来获得数据

这里我们提出一个新的概念:

  • DTO(Data Transfer Object)

这是一种设计模式之间传输数据的软件应用系统,它用于储存一些不属于同一个数据库中的一些信息

我们会新创一种DishDto实体类来继承Dish实体类并且新添一些关于DishFlavor的属性或方法来接收整个数据

在最后我们简单查看一下Dish和DishFlavor的数据表:

瑞吉外卖实战项目全攻略_spring_04

其中category_id表示所属分类的id

瑞吉外卖实战项目全攻略_java_05

其中dish_id表示该调味的菜的id

代码实现

首先我们来实现一个简单的功能,获得菜品分类并返回前端页面:

package com.qiuluo.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/category")
public class CategoryController {

@Autowired
private CategoryServiceImpl categoryService;

/**
* 返回所有分类名称
*/
@GetMapping("/list")
public Result<List<Category>> list(Category category){

// 创造一个LambdaQueryWrapper来判断Type=1并设置排序顺序
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(category.getType() != null,Category::getType,category.getType());
queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);

// 查询信息
List<Category> list = categoryService.list(queryWrapper);

// 返回信息
return Result.success(list);
}
}

接下来我们来实现存储双数据表到数据库的操作:

  1. 基本准备
# 首先我们来完成一些基本准备(我们的DishFlavor的操作也会放在DishController中实现)

实体类DishFlavor
数据层DishFlavorMapper
业务层接口DishFlavorService
业务层DishFlavorServiceImpl
服务层DishController
  1. 创建实体类DishDto(资料已提供)
// 我们通常单独创建一个包dto来装在DTO类

package com.qiuluo.reggie.dto;

import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.DishFlavor;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;

@Data
public class DishDto extends Dish {

// 在里面设置了List<DishFlavor> flavors 来装载 flavors
private List<DishFlavor> flavors = new ArrayList<>();

// categoryName 表示属于的分类名
private String categoryName;

private Integer copies;
}
  1. 新方法业务层接口实现
package com.qiuluo.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.DishFlavor;
import com.qiuluo.reggie.dto.DishDto;

public interface DishService extends IService<Dish> {

// 由于当前的默认方法无法满足我们的需求,我们创建新的方法来实现该操作

//新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish、dish_flavor
public void saveWithFlavor(DishDto dishDto);
}
  1. 新方法业务层实现
package com.qiuluo.reggie.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.DishFlavor;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.mapper.DishMapper;
import com.qiuluo.reggie.service.DishService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {

// 调入dishFlavor的业务层实现类
@Autowired
private DishFlavorServiceImpl dishFlavorService;

public void saveWithFlavor(DishDto dishDto){

// 1. 将菜品数据导入
this.save(dishDto);

// 2. 将Flavor导入(注意:Flavor传入时没有传入dishID,需要我们手动设置)
List<DishFlavor> flavors = dishDto.getFlavors();
for (DishFlavor flavor:flavors) {
flavor.setDishId(dishDto.getId());
}

dishFlavorService.saveBatch(flavors);

}

}
  1. 服务层实现
package com.qiuluo.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import com.qiuluo.reggie.service.impl.DishServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {

@Autowired
private DishServiceImpl dishService;

/**
* 新增菜品
* @param dishDto
* @return
*/
@PostMapping
public Result<String> save(@RequestBody DishDto dishDto){

dishService.saveWithFlavor(dishDto);

return Result.success("新创成功");
}

}

实际测试

点击打开新建菜品,填写数据点击后数据库出现相关菜品即可

菜品信息分页查询

我们的功能开发通常分为三部分

需求分析

系统中菜品很多时,我们需要采用分页查询,使菜品多批出现防止拥挤导致页面阅读不方便

这里我们先给出完成操作后的图片:

瑞吉外卖实战项目全攻略_数据_06

我们需要注意的是这里的菜品分类直接出了分类的名称

但是我们的Dish中只给出了菜品分类的id,所以我们还需要借用id去查出所属分类的名称并重新赋值

代码实现

首先我们来完成简单的分页查询操作:

package com.qiuluo.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import com.qiuluo.reggie.service.impl.DishServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {

@Autowired
private DishServiceImpl dishService;

@Autowired
private CategoryServiceImpl categoryService;

/**
* 分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public Result<Page> page(int page,int pageSize,String name){

// 构造基本Page
Page<Dish> pageImpl = new Page<>(page,pageSize);

// 进行查询
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(name != null,Dish::getName,name);
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

// 查询赋值,此时pageImpl里有值
dishService.page(pageImpl,queryWrapper);

return Result.success(pageImpl);

}

}

此时我们出来的页面中是无法查看到分类所属的:

瑞吉外卖实战项目全攻略_spring_07

所以我们需要设置包含有菜品分类名称的实体类作为Page的实现类参数才可以将菜品分类的名称传递到前端

我们只需要到前端代码中查看就可以注意到,商品分类这行上的数据属性名称为categoryName,所以我们采用DishDto来完成操作

我们将对代码进行修改:

package com.qiuluo.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import com.qiuluo.reggie.service.impl.DishServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {

@Autowired
private DishServiceImpl dishService;

@Autowired
private CategoryServiceImpl categoryService;

/**
* 分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public Result<Page> page(int page,int pageSize,String name){

// 构造基本Page
Page<Dish> pageImpl = new Page<>(page,pageSize);

// 因为返回类型中多了一个categoryName,我们还需要构造一个DishDto类型的Page
Page<DishDto> dishDtoPage = new Page<>();

// 进行查询
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(name != null,Dish::getName,name);
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

// 查询赋值,此时pageImpl里有值
dishService.page(pageImpl,queryWrapper);

// 但是我们需要返回DishDto类型的Page,我们将pageImpl的值赋值到dishDtoPage中(不要赋值records,这个值是数据,我们需要单独处理)
// 我们借助工具类实现
BeanUtils.copyProperties(pageImpl,dishDtoPage,"records");

// 然后我们来处理dishDtoPage中的records值,我们首先将pageImpl的records提取出来
List<Dish> records = pageImpl.getRecords();

// 将该值除了CategoryName全都赋值给dishDtoPage的records(我们这里使用stream流进行局内单个赋值,采用foreach方法也相同)
List<DishDto> dishDtoList = records.stream().map((item) -> {
// 创建一个新dishDto作为返回实体
DishDto dishDto = new DishDto();

// 将正常属性赋值进去
BeanUtils.copyProperties(item,dishDto);

// 将CategoryName复制进去
Long categoryId = item.getCategoryId();
Category category = categoryService.getById(categoryId);

if(category != null){
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}

return dishDto;
}).collect(Collectors.toList());

// 完成dishDtoPage的results的内容封装
dishDtoPage.setRecords(dishDtoList);

return Result.success(dishDtoPage);

}

}

标签:org,springframework,全攻略,瑞吉,外卖,import,reggie,qiuluo,com
From: https://blog.51cto.com/u_12617333/5785732

相关文章

  • 瑞吉外卖实战项目全攻略——第四天
    瑞吉外卖实战项目全攻略——第四天该系列将记录一份完整的实战项目的完成过程,该篇属于第四天案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容......
  • 瑞吉外卖实战项目全攻略——第三天
    瑞吉外卖实战项目全攻略——第三天该系列将记录一份完整的实战项目的完成过程,该篇属于第三天案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容......
  • 瑞吉外卖实战项目全攻略——第二天
    瑞吉外卖实战项目全攻略——第二天该系列将记录一份完整的实战项目的完成过程,该篇属于第二天案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容......
  • 瑞吉外卖 19日
    实体类实现Serializable的作用Serializable,之前一直有使用,默认的实体类就会实现Serializable接口,对具体原因一直不是很了解,同时如果没有实现序列化,同样没什么影响,什么时候......
  • 黑马瑞吉外卖之套餐信息的分页查询
    黑马瑞吉外卖之套餐信息的分页查询​​表和实体类环境以及前端页面分析​​​​后端代码的逻辑开发​​表和实体类环境以及前端页面分析首先这里是套餐的功能开发,我们在设置......
  • 瑞吉外卖实战项目全攻略——第一天
    瑞吉外卖实战项目全攻略——第一天该系列将记录一份完整的实战项目的完成过程,该篇属于第一天案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容......
  • Python dict字典方法完全攻略(全)
    我们知道,Python 字典的数据类型为dict,我们可使用 dir(dict) 来查看该类型包含哪些方法,例如:>>>dir(dict)['clear','copy','fromkeys','get','items','keys','po......
  • 瑞吉外卖项目记录
    通过过滤器实现用户状态检测功能描述:用户访问首页时,若为未登录状态,则跳转到登录页面功能实现:创建LoginCheckFilter类,添加@WebFilter注解,将该类声明为过滤器//fil......
  • Nero超刻DVDrip电影完全攻略
    很多人想收藏精彩的DVDrip电影,但往往电影文件加上字幕文件后,用正常的方法刻录一张700M的盘片就放不下了,这时光盘超刻就显得十分重要。准备工......
  • IIS 安装配置全攻略
    WEB篇Windows2000Server、Windows2000AdvancedServer以及Windows2000Professional的默认安装都带有IIS,也可以在Windows2000安装完毕后加装IIS。IIS......