1. 程式人生 > >Java電商專案面試--商品模組

Java電商專案面試--商品模組

面試:商品模組技術要點
1、POJO、BO、VO抽象模型
2、高效分頁及動態排序
3、FTP服務對接、富文字上傳

一、商品模組功能
前臺功能:
1、產品搜尋
2、動態排序列表
3、商品詳情
後臺功能:
1、商品列表
2、商品搜尋
3、圖片上傳
4、增加商品、更新商品、商品上下架
二、後臺新增和更新商品
Controller:

@Controller
@RequestMapping("/manage/product")
public class ProductManageController {
@Autowired
private IUserService iUserService;
@Autowired
private IProductService iProductService; @Autowired private IFileService iFileService; @RequestMapping("save.do") @ResponseBody public ServerResponse productSave(HttpSession session, Product product) { User user = (User) session.getAttribute(Const.CURRENT_USER); if (user == null) { return
ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "使用者未登入,請登入管理員"); } //如果是管理員 if (iUserService.checkAdminRole(user).isSuccess()) { //增加或者更新商品 return iProductService.saveOrUpdateProduct(product); } else { return ServerResponse.createByErrorMessage("無許可權操作"
); } }

Service層:

@Service("iProductService")
public class ProductServiceImpl implements IProductService {
    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private CategoryMapper categoryMapper;
    @Autowired
    private ICategoryService iCategoryService;

    //儲存或者更新商品
    public ServerResponse saveOrUpdateProduct(Product product){
        if(product != null)  //產品不為空
        {
            // 獲取前端傳來的子圖字串用;分割~譬如前端傳來1.jpg,2.jpg,3.jpg,
            // 然後第一塊程式碼就是將這個字串按照;來分割成字串陣列,就可以獲得所有子圖,
            // 然後把第一張子圖當做主圖來儲存
            if(StringUtils.isNotBlank(product.getSubImages())){
                String[] subImageArray = product.getSubImages().split(",");
                if(subImageArray.length > 0)
                    product.setMainImage(subImageArray[0]);
            }
            //當產品id存在則更新商品
            if(product.getId() != null){
                int rowCount = productMapper.updateByPrimaryKey(product);
                if(rowCount > 0)
                    return ServerResponse.createBySuccess("更新產品成功");
                return ServerResponse.createBySuccess("更新產品失敗");
            }
            //不存在則新增
            else
            {
                int rowCount = productMapper.insert(product);
                if(rowCount > 0)
                    return ServerResponse.createBySuccess("新增產品成功");
                return ServerResponse.createBySuccess("新增產品失敗");
            }
        }
        return ServerResponse.createByErrorMessage("新增或更新產品引數不正確");
    }
}

三、後臺新增和更新商品
Controller:

@RequestMapping("set_sale_status.do")
@ResponseBody
public ServerResponse setSaleStatus(HttpSession session, Integer productId,Integer status){
    User user = (User)session.getAttribute(Const.CURRENT_USER);
    if(user == null){
             return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),"使用者未登入,請登入管理員");
    }
    if(iUserService.checkAdminRole(user).isSuccess()){
            return iProductService.setSaleStatus(productId,status);
    }else{
            return ServerResponse.createByErrorMessage("無許可權操作");
    }
}

Service:

public ServerResponse<String> setSaleStatus(Integer productId,Integer status){
    if(productId == null || status == null){
             return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(),ResponseCode.ILLEGAL_ARGUMENT.getDesc());
    }
    Product product = new Product();
    product.setId(productId);
    product.setStatus(status);
    int rowCount = productMapper.updateByPrimaryKeySelective(product);
    if(rowCount > 0){
            return ServerResponse.createBySuccess("修改產品銷售狀態成功");
    }
    return ServerResponse.createByErrorMessage("修改產品銷售狀態失敗");
}

四、後臺獲取產品詳情
Controller:

@RequestMapping("detail.do")
@ResponseBody
public ServerResponse getDetail(HttpSession session, Integer productId){
    User user = (User)session.getAttribute(Const.CURRENT_USER);
    if(user == null)
        return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),"使用者未登入,請登入管理員");
    if(iUserService.checkAdminRole(user).isSuccess()){
        return iProductService.manageProductDetail(productId);
    }else{
        return ServerResponse.createByErrorMessage("無許可權操作");
    }
}

Service:

@Service("iProductService")
public class ProductServiceImpl implements IProductService {
      @Autowired
      private ProductMapper productMapper; 
      @Autowired
      private CategoryMapper categoryMapper;
      @Autowired
      private ICategoryService iCategoryService;

public ServerResponse<ProductDetailVo> manageProductDetail(Integer productId){
    if(productId == null){
        return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(),ResponseCode.ILLEGAL_ARGUMENT.getDesc());
    }
    Product product = productMapper.selectByPrimaryKey(productId);
    if(product == null)
        return ServerResponse.createByErrorMessage("產品已下架或者刪除");
    //VO -- value object
    ProductDetailVo productDetailVo = assembleProductDetailVo(product);
    return ServerResponse.createBySuccess(productDetailVo);
}
private ProductDetailVo assembleProductDetailVo(Product product){
    ProductDetailVo productDetailVo = new ProductDetailVo();
    productDetailVo.setId(product.getId());
    productDetailVo.setSubtitle(product.getSubtitle());
    productDetailVo.setPrice(product.getPrice());
    productDetailVo.setMainImage(product.getMainImage());
    productDetailVo.setSubImages(product.getSubImages());
    productDetailVo.setCategoryId(product.getCategoryId());
    productDetailVo.setDetail(product.getDetail());
    productDetailVo.setName(product.getName());
    productDetailVo.setStatus(product.getStatus());
    productDetailVo.setStock(product.getStock());
    productDetailVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://image.long.com/"));

    Category category = categoryMapper.selectByPrimaryKey(product.getCategoryId());
    if(category == null){
        productDetailVo.setParentCategoryId(0);//預設根節點
    }else{
        productDetailVo.setParentCategoryId(category.getParentId());
    }
    productDetailVo.setCreateTime(DateTimeUtil.dateToStr(product.getCreateTime()));
    productDetailVo.setUpdateTime(DateTimeUtil.dateToStr(product.getUpdateTime()));
    return productDetailVo;
}

VO:

public class ProductDetailVo {
    private Integer  id;
    private Integer categoryId;
    private String name;
    private String subtitle;
    private String mainImage;
    private String subImages;
    private String detail;
    private BigDecimal price;
    private Integer stock;
    private Integer status;
    private String createTime;
    private String updateTime;

    /*******相對於Product物件對出來的屬性******/
    private String imageHost;  //圖片伺服器url字首
    private Integer parentCategoryId;  //父分類
    //...setter和getter方法...
 }

簡單分析下VO:
包裝類其實就是我們專案中的pojo類,欄位與資料庫表的欄位是相同的,而vo類可以簡單理解成專門用於展示給使用者看的類。
這裡使用VO,這樣可以減少大量的工作量(也就意味著減少bug,減少風險),也不需要擔心未來的維護工作!VO還有一個好處就是是例如時間型別,返回給前臺的時候需要轉String型別,pojo轉vo是為了封裝,例如時間做成字串,或者列舉轉換成漢字,或者增加其他屬性,這樣vo的靈活性就突顯出來了。

使用流讀取配置properties配置檔案:
基礎部分可以看這個:Java基礎系列(二十)Properties類、commons-IO包
1、為什麼使用propertie
有些常量需要動態的配置,如果專案上線後,每次修改Constants.java然後再編譯,再上傳Constants.class檔案,再重啟伺服器。這樣導致很繁瑣。
如果將需要修改的配置項寫成properties檔案,程式寫好後,以後要修改資料,直接在配置檔案裡修改,程式就不用修改,也不用重新編譯了,將會在專案後期維護帶來很大的方便~!
2、propertie檔案讀取方式
① 基於 ClassLoader 讀取配置檔案:
注意:該方式只能讀取類路徑下的配置檔案,有侷限但是如果配置檔案在類路徑下比較方便。

Properties properties = new Properties();
// 使用ClassLoader載入properties配置檔案生成對應的輸入流
InputStream in = PropertiesMain.class.getClassLoader().getResourceAsStream("config/config.properties");
// 使用properties物件載入輸入流
properties.load(in);
//獲取key對應的value值
properties.getProperty(String key);

② 基於 InputStream 讀取配置檔案:
注意:該方式的優點在於可以讀取任意路徑下的配置檔案

Properties properties = new Properties();
// 使用InPutStream流讀取properties檔案
BufferedReader bufferedReader = new BufferedReader(new FileReader("E:/config.properties"));
properties.load(bufferedReader);
// 獲取key對應的value值
properties.getProperty(String key);

③ 通過 java.util.ResourceBundle 類來讀取,這種方式比使用 Properties 要方便一些:
注意:該方式的優點在於這種方式來獲取properties屬性檔案不需要加.properties字尾名,只需要檔名即可

properties.getProperty(String key);
//config為屬性檔名,放在包com.test.config下,如果是放在src下,直接用config即可  
ResourceBundle resource = ResourceBundle.getBundle("com/test/config/config");
String key = resource.getString("keyWord"); 

因為配置檔案在類路徑下使用第一種方式比較方便,所以本專案採用的是第一種方式:

public class PropertiesUtil {
    private static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class);
    private static Properties props;
    static {    //讀取配置
        String fileName = "mmall.properties";
        props = new Properties();
        try {
            props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8"));
        } catch (IOException e) {
            logger.error("配置檔案讀取異常",e);
        }
    }
    public static String getProperty(String key){
        String value = props.getProperty(key.trim());
        if(StringUtils.isBlank(value))
            return null;
        return value.trim();
    }
    //如果值為空,返回defaultValue
    public static String getProperty(String key,String defaultValue){
        String value = props.getProperty(key.trim());
        if(StringUtils.isBlank(value))
            value = defaultValue;
        return value.trim();
    }
}

joda-time和JDK自帶的SimpleDateformat的區別是什麼?
因為jdk的Date/Calender等api使用具有一定的難度,且jdk預設的有多執行緒問題,Joda-Time 令時間和日期值變得易於管理、操作和理解。事實上,易於使用是 Joda 的主要設計目標。其他目標包括可擴充套件性、完整的特性集以及對多種日曆系統的支援。並且 Joda 與 JDK 是百分之百可互操作的。

public class DateTimeUtil {
    public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
    //字串轉時間
    public static Date strToDate(String dateTimeStr){
        DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
        DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
        return dateTime.toDate();
    }
    //時間轉字串
    public static String dateToStr(Date date){
        if(date == null)  return StringUtils.EMPTY;
        DateTime dateTime = new DateTime(date);
        return dateTime.toString(STANDARD_FORMAT);
    }
    //測試
    public static void main(String[] args) {
        System.out.println(DateTimeUtil.dateToStr(new Date(),"yyyy-MM-dd HH:mm:ss"));
        System.out.println(DateTimeUtil.strToDate("2010-01-01 11:11:11","yyyy-MM-dd HH:mm:ss"));
    }
}

五、後臺商品列表
Controller:

//商品列表
@RequestMapping("list.do")
@ResponseBody
public ServerResponse getList(HttpSession session, @RequestParam(value = "pageNum",defaultValue = "1") int pageNum,@RequestParam(value = "pageSize",defaultValue = "10") int pageSize){
    User user = (User)session.getAttribute(Const.CURRENT_USER);
    if(user == null)
        return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),"使用者未登入,請登入管理員");
    if(iUserService.checkAdminRole(user).isSuccess())
        return iProductService.getProductList(pageNum,pageSize);  //頁數,每頁容量
    else
        return ServerResponse.createByErrorMessage("無許可權操作");
}

Service:

//後臺獲取商品列表 頁數 每頁多少條資料
public ServerResponse<PageInfo> getProductList(int pageNum,int pageSize){
    /*********使用步驟:*******
     * 1、startPage
     * 2、填充自己的sql查詢邏輯
     * 3、pageHelper-收尾
     *************************/
    //1、startPage
    PageHelper.startPage(pageNum,pageSize);   //頁數,每頁容量
    //2、填充自己的sql查詢邏輯
    List<Product> productList = productMapper.selectList();  //查詢產品列表
    List<ProductListVo> productListVoList = Lists.newArrayList();
    for(Product productItem : productList){
        ProductListVo productListVo = assembleProductListVo(productItem);
        productListVoList.add(productListVo);
    }
    //3、pageHelper-收尾
    PageInfo pageResult = new PageInfo(productList);
    pageResult.setList(productListVoList);
    //使用PageInfo包裝查詢結果,只需要將pageInfo交給頁面就可以
    //使用PageInfo這個類,你需要將查詢出來的list放進去:
    return ServerResponse.createBySuccess(pageResult);
}
//ProducyList的組裝方法
private ProductListVo assembleProductListVo(Product product){
    ProductListVo productListVo = new ProductListVo();
    productListVo.setId(product.getId());
    productListVo.setName(product.getName());
    productListVo.setCategoryId(product.getCategoryId());
    productListVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://image.long.com/"));
    productListVo.setMainImage(product.getMainImage());
    productListVo.setPrice(product.getPrice());
    productListVo.setSubtitle(product.getSubtitle());
    productListVo.setStatus(product.getStatus());
    return productListVo;
}

Mapper.xml:

<!--獲取商品列表-->
<select id="selectList" resultMap="BaseResultMap" >
  SELECT
  <include refid="Base_Column_List"/>
  from mmall_product
  ORDER BY id asc
</select>

Mybatis配置:

<!-- spring和MyBatis完美整合,不需要mybatis的配置對映檔案 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"></property>

    <!-- 分頁外掛 -->
    <property name="plugins">
        <array>
            <bean class="com.github.pagehelper.PageHelper">
                <property name="properties">
                    <value>
                        dialect=mysql
                    </value>
                </property>
            </bean>
        </array>
    </property>
</bean>

<!--掃描mapper-->
<bean name="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--基本包,全部放介面-->
    <property name="basePackage" value="com.mmall.dao"/>
</bean>

PageInfo底層:

//當前頁
private int pageNum;
//每頁的數量
private int pageSize;
//當前頁的數量
private int size;
//排序
private String orderBy;

//由於startRow和endRow不常用,這裡說個具體的用法
//可以在頁面中"顯示startRow到endRow 共size條資料"

//當前頁面第一個元素在資料庫中的行號
private int startRow;
//當前頁面最後一個元素在資料庫中的行號
private int endRow;
//總記錄數
private long total;
//總頁數
private int pages;
//結果集
private List<T> list;

//第一頁
private int firstPage;
//前一頁
private int prePage;
//下一頁
private int nextPage;
//最後一頁
private int lastPage;

//是否為第一頁
private boolean isFirstPage = false;
//是否為最後一頁
private boolean isLastPage = false;
//是否有前一頁
private boolean hasPreviousPage = false;
//是否有下一頁
private boolean hasNextPage = false;
//導航頁碼數
private int navigatePages;
//所有導航頁號
private int[] navigatepageNums;

Mybatis流程
這裡寫圖片描述
從圖中可以看出,mybatis中首先要在配置檔案中配置一些東西,然後根據這些配置去建立一個會話工廠,再根據會話工廠建立會話,會話發出操作資料庫的sql語句,然後通過執行器操作資料,再使用mappedStatement對資料進行封裝,這就是整個mybatis框架的執行情況。那麼mybatis的外掛作用在哪一環節呢?它主要作用在Executor執行器與mappedeStatement之間,也就是說mybatis可以在外掛中獲得要執行的sql語句,在sql語句中新增limit語句,然後再去對sql進行封裝,從而可以實現分頁處理。
動態分頁
pageHelper分頁的底層主要是通過 aop來實現,在執行sql之前會在sql語句中新增limit offset這兩個引數。這樣就完成了動態的分頁。
我們需要用vo返回給前端。如果我們用vo裡的欄位,是和pojo總會有不一致的地方。例如時間的型別,又例如新增的一些列舉狀態等。那麼為了自動分頁,我們會用dao層找到原始的pojoList,(因為pageHelper是對dao層在執行mapper的時候才會動態分頁,所以我們要先執行一下mapper)然後轉換成vo。那麼其實這兩個list的集合的分頁引數是一致的。所以用了一個比較巧妙的辦法,來把vo進行分頁。
六、後臺商品搜尋功能
Controller:

//商品搜尋功能
@RequestMapping("search.do")
@ResponseBody
public ServerResponse productSearch(HttpSession session,String productName,Integer productId, @RequestParam(value = "pageNum",defaultValue = "1") int pageNum,@RequestParam(value = "pageSize",defaultValue = "10") int pageSize){
    User user = (User)session.getAttribute(Const.CURRENT_USER);
    if(user == null)
        return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),"使用者未登入,請登入管理員");
    if(iUserService.checkAdminRole(user).isSuccess())
        return iProductService.searchProduct(productName,productId,pageNum,pageSize);  //填充業務
    else
        return ServerResponse.createByErrorMessage("無許可權操作");
}

Service:

 //商品搜尋功能
public ServerResponse<PageInfo> searchProduct(String productName,Integer productId,int pageNum,int pageSize){
    PageHelper.startPage(pageNum,pageSize);
    if(StringUtils.isNotBlank(productName))     //商品名字
        productName = new StringBuilder().append("%").append(productName).append("%").toString(); //%商品名字%
    List<Product> productList = productMapper.selectByNameAndProductId( productName , productId );
    List<ProductListVo> productListVoList = Lists.newArrayList();
    for(Product productItem : productList){
        ProductListVo productListVo = assembleProductListVo(productItem);
        productListVoList.add(productListVo);
    }
    PageInfo pageResult = new PageInfo(productList);
    pageResult.setList(productListVoList);
    return ServerResponse.createBySuccess(pageResult);
}

Mapper.xml:

<!--//通過名字或者id查詢商品-->
<select id="selectByNameAndProductId" resultMap="BaseResultMap" parameterType="map">
  SELECT
  <include refid="Base_Column_List"/>
      from mmall_product
      <where>
           <if test="productName != null">
                and name like #{productName}
           </if>
           <if test="productId != null">
                and id = #{productId}
           </if>
      </where>
</select>

七、後臺商品圖片上傳功能
Controller:

//上傳檔案功能
@RequestMapping("upload.do")
@ResponseBody
public ServerResponse upload(HttpSession session,@RequestParam(value = "upload_file",required = false) MultipartFile file,HttpServletRequest request){
    User user = (User)session.getAttribute(Const.CURRENT_USER);
    if(user == null)
        return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),"使用者未登入,請登入管理員");
    if(iUserService.checkAdminRole(user).isSuccess()){   //檢查是否是管理員
        String path = request.getSession().getServletContext().getRealPath("upload");
        String targetFileName = iFileService.upload(file,path);   //上傳檔案
        String url = PropertiesUtil.getProperty("ftp.server.http.prefix")+targetFileName;

        Map fileMap = Maps.newHashMap();
        fileMap.put("uri",targetFileName);
        fileMap.put("url",url);

        return ServerResponse.createBySuccess(fileMap);   //返回給前端
    }else{
        return ServerResponse.createByErrorMessage("無許可權操作");
    }
}

Service:

@Service("iFileService")
public class FileServiceImpl implements IFileService {

    private Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);

    public String upload(MultipartFile file,String path){   //檔案和路徑
        String fileName = file.getOriginalFilename();   //獲取檔案的名字
        String fileExtensionName = fileName.substring(fileName.lastIndexOf(".")+1);  //獲取副檔名,例如檔名為:abc.jpg
        String uploadFileName = UUID.randomUUID().toString()+"."+fileExtensionName;  //上傳檔案的名字,隨機字串+副檔名
        logger.info("開始上傳檔案,上傳檔案的檔名:{},上傳的路徑:{},新檔名:{}",fileName,path,uploadFileName);

        File fileDir = new File(path);  //建立目錄
        if(!fileDir.exists()){
            fileDir.setWritable(true);
            fileDir.mkdirs();
        }
        File targetFile = new File(path,uploadFileName);   //建立檔案(假設在upload資料夾下)
        try {
            file.transferTo(targetFile);  //檔案已經上傳成功到了upload資料夾
            FTPUtil.uploadFile(Lists.newArrayList(targetFile));  //已經上傳到ftp伺服器上
            targetFile.delete();   //上傳成功之後,就刪除建立的的檔案,如upload下面的檔案
        } catch (IOException e) {
            logger.error("上傳檔案異常",e);
            return null;
        }
        return targetFile.getName();
    }
}

FTPUtil:

public class FTPUtil {
private static  final Logger logger = LoggerFactory.getLogger(FTPUtil.class);

private static String ftpIp = PropertiesUtil.getProperty("ftp.server.ip");
private static String ftpUser = PropertiesUtil.getProperty("ftp.user");
private static String ftpPass = PropertiesUtil.getProperty("ftp.pass");

public FTPUtil(String ip,int port,String user,String pwd){   //構造器
    this.ip = ip;
    this.port = port;
    this.user = user;
    this.pwd = pwd;
}
//上傳檔案
public static boolean uploadFile(List<File> fileList) throws IOException {
    FTPUtil ftpUtil = new FTPUtil(ftpIp,21,ftpUser,ftpPass);
    logger.info("開始連線ftp伺服器");
    boolean result = ftpUtil.uploadFile("img",fileList);     //上傳檔案到FTP伺服器下面的img目錄下
    logger.info("開始連線ftp伺服器,結束上傳,上傳結果:{}");
    return result;
}
//上傳檔案
private boolean uploadFile(String remotePath,List<File> fileList) throws IOException {  //遠端路徑
    boolean uploaded = true;     //是否傳了
    FileInputStream fis = null;
    if(connectServer(this.ip,this.port,this.user,this.pwd)){  //連線FTP伺服器
        try {
            ftpClient.changeWorkingDirectory(remotePath);
            ftpClient.setBufferSize(1024);
            ftpClient.setControlEncoding("UTF-8");
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            ftpClient.enterLocalPassiveMode();
            for(File fileItem : fileList){
                fis = new FileInputStream(fileItem);
                ftpClient.storeFile(fileItem.getName(),fis);
            }
        } catch (IOException e) {
            logger.error("上傳檔案異常",e);
            uploaded = false;
            e.printStackTrace();
        } finally {
            fis.close();
            ftpClient.disconnect();
        }
    }
    return uploaded;
}
//連線伺服器函式
private boolean connectServer(String ip,int port,String user,String pwd){
    boolean isSuccess = false;
    ftpClient = new FTPClient();
    try {
        ftpClient.connect(ip);
        isSuccess = ftpClient.login(user,pwd);
    } catch (IOException e) {
        logger.error("連線FTP伺服器異常",e);
    }
    return isSuccess;
}

八、後臺富文字中圖片上傳
Controller:

//富文字中圖片上傳
@RequestMapping("richtext_img_upload.do")
@ResponseBody
public Map richtextImgUpload(HttpSession session, @RequestParam(value = "upload_file",required = false) MultipartFile file, HttpServletRequest request, HttpServletResponse response){
    Map resultMap = Maps.newHashMap();
    User user = (User)session.getAttribute(Const.CURRENT_USER);
    if(user == null){
        resultMap.put("success",false);
        resultMap.put("msg","請登入管理員");
        return resultMap;
    }
    /**
         富文字中對於返回值有自己的要求,我們使用是simditor所以按照simditor的要求進行返回
        {
            "success": true/false,
            "msg": "error message", # optional  是可選的
            "file_path": "[real file path]"
        }
    **/
    if(iUserService.checkAdminRole(user).isSuccess()){
        String path = request.getSession().getServletContext().getRealPath("upload");
        String targetFileName = iFileService.upload(file,path);
        if(StringUtils.isBlank(targetFileName)){
            resultMap.put("success",false);
            resultMap.put("msg","上傳失敗");
            return resultMap;
        }
        String url = PropertiesUtil.getProperty("ftp.server.http.prefix")+targetFileName;
        resultMap.put("success",true);
        resultMap.put("msg","上傳成功");
        resultMap.put("file_path",url);
        response.addHeader("Access-Control-Allow-Headers","X-File-Name");
        return resultMap;
    }else{
        resultMap.put("success",false);
        resultMap.put("msg","無許可權操作");
        return resultMap;
    }
}

八、前臺功能
Controller:

//查詢商品詳情
@RequestMapping("detail.do")
@ResponseBody
public ServerResponse<ProductDetailVo> detail(Integer productId){
    return iProductService.getProductDetail(productId);
}
//商品搜尋
@RequestMapping("list.do")
@ResponseBody
public ServerResponse<PageInfo> list(@RequestParam(value = "keyword",required = false)String keyword,
                                     @RequestParam(value = "categoryId",required = false)Integer categoryId,
                                     @RequestParam(value = "pageNum",defaultValue = "1") int pageNum,
                                     @RequestParam(value = "pageSize",defaultValue = "10") int pageSize,
                                     @RequestParam(value = "orderBy",defaultValue = "") String orderBy){
    return iProductService.getProductByKeywordCategory(keyword,categoryId,pageNum,pageSize,orderBy);
}

Service:

//前臺獲取商品資訊
public ServerResponse<ProductDetailVo> getProductDetail(Integer productId){
    if(productId == null)
        return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(),ResponseCode.ILLEGAL_ARGUMENT.getDesc());
    Product product = productMapper.selectByPrimaryKey(productId);
    if(product == null)
        return ServerResponse.createByErrorMessage("產品已下架或者刪除");
    if(product.getStatus() != Const.ProductStatusEnum.ON_SALE.getCode())
        return ServerResponse.createByErrorMessage("產品已下架或者刪除");
    ProductDetailVo productDetailVo = assembleProductDetailVo(product);
    return ServerResponse.createBySuccess(productDetailVo);
}

//獲取商品列表,搜尋商品,排序
public ServerResponse<PageInfo> getProductByKeywordCategory(String keyword,Integer categoryId,int pageNum,int pageSize,String orderBy){
    if(StringUtils.isBlank(keyword) && categoryId == null)
        return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(),ResponseCode.ILLEGAL_ARGUMENT.getDesc());
    List<Integer> categoryIdList = new ArrayList<Integer>();
    if(categoryId != null){
        Category category = categoryMapper.selectByPrimaryKey(categoryId);  //獲取分類
        if(category == null && StringUtils.isBlank(keyword)){  //沒有該分類,並且還沒有關鍵字,這個時候返回一個空的結果集,不報錯
            PageHelper.startPage(pageNum,pageSize);
            List<ProductListVo> productListVoList = Lists.newArrayList();
            PageInfo pageInfo = new PageInfo(productListVoList);
            return ServerResponse.createBySuccess(pageInfo);
        }
        categoryIdList = iCategoryService.selectCategoryAndChildrenById(category.getId()).getData();  //根據id查詢此節點id和其子節點id的集合
    }

    if(StringUtils.isNotBlank(keyword))
        keyword = new StringBuilder().append("%").append(keyword).append("%").toString();  //%關鍵字%

    PageHelper.startPage( pageNum , pageSize );
    //排序處理
    if(StringUtils.isNotBlank(orderBy)){  //如果排序關鍵字不為空
        if(Const.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)){  //price_desc
            String[] orderByArray = orderBy.split("_");
            PageHelper.orderBy(orderByArray[0]+" "+orderByArray[1]);  //格式為price desc
        }
    }

    List<Product> productList = productMapper.selectByNameAndCategoryIds(StringUtils.isBlank(keyword)?null:keyword,categoryIdList.size()==0?null:categoryIdList);

    List<ProductListVo> productListVoList = Lists.newArrayList();
    for(Product product : productList){
        ProductListVo productListVo = assembleProductListVo(product);    //封裝為VO
        productListVoList.add(productListVo);
    }

    PageInfo pageInfo = new PageInfo(productList);
    pageInfo.setList(productListVoList);
    return ServerResponse.createBySuccess(pageInfo);
}

動態排序
兩種方法:
1、方式1

//其中A為排序依據的欄位名,B為排序規律,desc為降序,asc為升序
PageHelper.startPage(pageNum , pageSize);
PageHelper.orderBy("A B");

2、方式2

//其中A為排序依據的欄位名,B為排序規律,desc為降序,asc為升序
String orderBy="A B";
PageHelper.startPage(pageNum, pageSize, orderBy);

九、FTP伺服器
1、FTP是什麼?
FTP是File Transfer Protocol(檔案傳輸協議)的英文簡稱,用於Internet上的控制檔案的雙向傳輸。簡單的說,支援FTP協議的伺服器就是FTP伺服器。FTP是一個客戶機/伺服器系統。使用者通過一個支援FTP協議的客戶機程式,連線到在遠端主機上的FTP伺服器程式。使用者通過客戶機程式向伺服器程式發出命令,伺服器程式執行使用者所發出的命令,並將執行的結果返回到客戶機。
2、為什麼使用FTP伺服器
從一個小網站說起。一臺伺服器也就足夠了。檔案伺服器,資料庫,還有應用都部署在一臺機器,俗稱ALL IN ONE。隨著我們使用者越來越多,訪問越來越大,硬碟,CPU,記憶體等都開始吃緊。一臺伺服器已經滿足不了,我們將資料服務和應用服務分離,給應用伺服器配置更好的 CPU,記憶體。而給資料伺服器配置更好更大的硬碟。分離之後提高一定的可用性,例如Files Server掛了,我們還是可以操作應用和資料庫等。
這裡寫圖片描述
3、對比FTP和FastDFS
單獨部署的檔案伺服器是網際網路專案必不可少的一項。簡單的話可以採用FTP做檔案伺服器,但是專案訪問量持續增加的話,必要考慮檔案伺服器的擴充套件性與高可用。這時候採用FastDFS是比較明智的,當伺服器容量不夠用時,FastDFS可以快速的進行線性擴容。FastDFS是用c語言編寫的一款開源的分散式檔案系統。FastDFS為網際網路量身定製,充分考慮了冗餘備份、負載均衡、線性擴容等機制,並注重高可用、高效能等指標,使用FastDFS很容易搭建一套高效能的檔案伺服器叢集提供檔