栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

从0开始独立完成企业级Java电商网站开发(服务端)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

从0开始独立完成企业级Java电商网站开发(服务端)

数据表结构设计 唯一索引unique,保证数据唯一性
CREATE TABLE `mmall_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户表id',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(50) NOT NULL COMMENT '用户密码,MD5加密',
  `email` varchar(50) DEFAULT NULL,
  `phone` varchar(20) DEFAULT NULL,
  `question` varchar(100) DEFAULT NULL COMMENT '找回密码问题',
  `answer` varchar(100) DEFAULT NULL COMMENT '找回密码答案',
  `role` int(4) NOT NULL COMMENT '角色0-管理员,1-普通用户',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '最后一次更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_name_unique` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;

单索引及组合索引
CREATE TABLE `mmall_order_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单子表id',
  `user_id` int(11) DEFAULT NULL,
  `order_no` bigint(20) DEFAULT NULL,
  `product_id` int(11) DEFAULT NULL COMMENT '商品id',
  `product_name` varchar(100) DEFAULT NULL COMMENT '商品名称',
  `product_image` varchar(500) DEFAULT NULL COMMENT '商品图片地址',
  `current_unit_price` decimal(20,2) DEFAULT NULL COMMENT '生成订单时的商品单价,单位是元,保留两位小数',
  `quantity` int(10) DEFAULT NULL COMMENT '商品数量',
  `total_price` decimal(20,2) DEFAULT NULL COMMENT '商品总价,单位是元,保留两位小数',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `order_no_index` (`order_no`) USING BTREE,
  KEY `order_no_user_id_index` (`user_id`,`order_no`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=135 DEFAULT CHARSET=utf8;

查业务问题的后悔药

create_time 数据创建时间
update_time 数据更新时间

mybatis三剑客 mybatis-generator自动化生成数据库交互代码

配置pom.xml


    mmall
    
      
 org.mybatis.generator
 mybatis-generator-maven-plugin
 1.3.2
 
   true
   true
 
      
  

datasource.properties

db.driverLocation=/Users/imooc/mysql-connector-java-5.1.6-bin.jar
db.driverClassName=com.mysql.jdbc.Driver

#db.url=jdbc:mysql://192.1.1.1:3306/mmall?characterEncoding=utf-8
db.url=jdbc:mysql://你的数据库IP:你的数据库Port/你的database?characterEncoding=utf-8
db.username=mmall
db.password=dbpassword


db.initialSize = 20
db.maxActive = 50
db.maxIdle = 20
db.minIdle = 10
db.maxWait = 10
db.defaultAutoCommit = true
db.minEvictableIdleTimeMillis = 3600000


generatorConfig.xml





    
    

    
    

    

 
 
     
     
 

 
 
 


 
 
     
 


 
 
 
     
     
     
     
     
     
     
     
 

 
 
 
     
 

 

 
 
 
     
     
 


 

运行

mybatis-plugin idea插件,实现mybatis的接口文件和xml自动跳转

下载

mybatis-pagehelper mybatis分页组件

配置pom.xml

	

    
      com.github.pagehelper
      pagehelper
      4.1.0
    

    
      com.github.miemiedev
      mybatis-paginator
      1.2.17
    

    
      com.github.jsqlparser
      jsqlparser
      0.9.4
    


Tomcat加载spring、springmvc

方式一:配置web.xml

	



	
		login.jsp
	
	
	
	
		com.atguigu.scw.manager.listener.MyAppListener
	

	
	
	
		contextConfigLocation
		classpath:spring-*.xml
	

	
	
		org.springframework.web.context.ContextLoaderListener
	


	
	
		springDispatcherServlet
		org.springframework.web.servlet.DispatcherServlet
		
			contextConfigLocation
			classpath:springmvc.xml
		
		1
	

	
	
		springDispatcherServlet
		/
	

	
	
		CharacterEncodingFilter
		org.springframework.web.filter.CharacterEncodingFilter
		
		
			encoding
			utf-8
		
		
		
			forceRequestEncoding
			true
		
		
			forceResponseEncoding
			true
		
	
	
		CharacterEncodingFilter
		
public class MD5Util {

    private static String byteArrayToHexString(byte b[]) {
 StringBuffer resultSb = new StringBuffer();
 for (int i = 0; i < b.length; i++)
     resultSb.append(byteToHexString(b[i]));

 return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
 int n = b;
 if (n < 0)
     n += 256;
 int d1 = n / 16;
 int d2 = n % 16;
 return hexDigits[d1] + hexDigits[d2];
    }

    
    private static String MD5Encode(String origin, String charsetname) {
 String resultString = null;
 try {
     resultString = new String(origin);
     MessageDigest md = MessageDigest.getInstance("MD5");
     if (charsetname == null || "".equals(charsetname))
  resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
     else
  resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
 } catch (Exception exception) {
 }
 return resultString.toUpperCase();
    }

    public static String MD5EncodeUtf8(String origin) {
 origin = origin + PropertiesUtil.getProperty("password.salt", "");
 return MD5Encode(origin, "utf-8");
    }


    private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
     "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

}


PropertiesUtil.java

读取src/main/resources目录下的配置文件

package com.mmall.util;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;


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();
    }

    public static String getProperty(String key,String defaultValue){

 String value = props.getProperty(key.trim());
 if(StringUtils.isBlank(value)){
     value = defaultValue;
 }
 return value.trim();
    }

}


mmall.properties

ftp.server.ip=你的FTP服务器ip地址
ftp.user=mmallftp
ftp.pass=ftppassword
ftp.server.http.prefix=http://img.happymmall.com/


alipay.callback.url=http://www.happymmall.com/order/alipay_callback.do

password.salt = geelysdafaqj23ou89ZXcj@#$@#$#@KJdjklj;D../dSF.,


应用:明文加密

guava缓存

设置token并传给前台用户

验证token

TokenCache.java

package com.mmall.common;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;


public class TokenCache {

    private static Logger logger = LoggerFactory.getLogger(TokenCache.class);

    public static final String TOKEN_PREFIX = "token_";

    //LRU算法
    private static LoadingCache localCache = CacheBuilder.newBuilder().initialCapacity(1000).maximumSize(10000).expireAfterAccess(12, TimeUnit.HOURS)
     .build(new CacheLoader() {
  //默认的数据加载实现,当调用get取值的时候,如果key没有对应的值,就调用这个方法进行加载.
  @Override
  public String load(String s) throws Exception {
      return "null";
  }
     });

    public static void setKey(String key,String value){
 localCache.put(key,value);
    }

    public static String getKey(String key){
 String value = null;
 try {
     value = localCache.get(key);
     if("null".equals(value)){
  return null;
     }
     return value;
 }catch (Exception e){
     logger.error("localCache get error",e);
 }
 return null;
    }
}



UserServiceImpl.java

public ServerResponse checkAnswer(String username,String question,String answer){
 int resultCount = userMapper.checkAnswer(username,question,answer);
 if(resultCount>0){
     //说明问题及问题答案是这个用户的,并且是正确的
     String forgetToken = UUID.randomUUID().toString();
     TokenCache.setKey(TokenCache.TOKEN_PREFIX+username,forgetToken);
     return ServerResponse.createBySuccess(forgetToken);
 }
 return ServerResponse.createByErrorMessage("问题的答案错误");
    }



    public ServerResponse forgetResetPassword(String username,String passwordNew,String forgetToken){
 if(org.apache.commons.lang3.StringUtils.isBlank(forgetToken)){
     return ServerResponse.createByErrorMessage("参数错误,token需要传递");
 }
 ServerResponse validResponse = this.checkValid(username,Const.USERNAME);
 if(validResponse.isSuccess()){
     //用户不存在
     return ServerResponse.createByErrorMessage("用户不存在");
 }
 String token = TokenCache.getKey(TokenCache.TOKEN_PREFIX+username);
 if(org.apache.commons.lang3.StringUtils.isBlank(token)){
     return ServerResponse.createByErrorMessage("token无效或者过期");
 }

 if(org.apache.commons.lang3.StringUtils.equals(forgetToken,token)){
     String md5Password  = MD5Util.MD5EncodeUtf8(passwordNew);
     int rowCount = userMapper.updatePasswordByUsername(username,md5Password);

     if(rowCount > 0){
  return ServerResponse.createBySuccessMessage("修改密码成功");
     }
 }else{
     return ServerResponse.createByErrorMessage("token错误,请重新获取重置密码的token");
 }
 return ServerResponse.createByErrorMessage("修改密码失败");
    }


高复用服务响应对象的设计思想和封装

ServerResponse.java

package com.mmall.common;

import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.map.annotate.JsonSerialize;

import java.io.Serializable;


@JsonSerialize(include =  JsonSerialize.Inclusion.NON_NULL)
//保证序列化json的时候,如果是null的对象,key也会消失
public class ServerResponse implements Serializable {

    private int status;
    private String msg;
    private T data;

    private ServerResponse(int status){
 this.status = status;
    }
    private ServerResponse(int status,T data){
 this.status = status;
 this.data = data;
    }

    private ServerResponse(int status,String msg,T data){
 this.status = status;
 this.msg = msg;
 this.data = data;
    }

    private ServerResponse(int status,String msg){
 this.status = status;
 this.msg = msg;
    }

    @JsonIgnore
    //使之不在json序列化结果当中
    public boolean isSuccess(){
 return this.status == ResponseCode.SUCCESS.getCode();
    }

    public int getStatus(){
 return status;
    }
    public T getData(){
 return data;
    }
    public String getMsg(){
 return msg;
    }


    public static  ServerResponse createBySuccess(){
 return new ServerResponse(ResponseCode.SUCCESS.getCode());
    }

    public static  ServerResponse createBySuccessMessage(String msg){
 return new ServerResponse(ResponseCode.SUCCESS.getCode(),msg);
    }

    public static  ServerResponse createBySuccess(T data){
 return new ServerResponse(ResponseCode.SUCCESS.getCode(),data);
    }

    public static  ServerResponse createBySuccess(String msg,T data){
 return new ServerResponse(ResponseCode.SUCCESS.getCode(),msg,data);
    }


    public static  ServerResponse createByError(){
 return new ServerResponse(ResponseCode.ERROR.getCode(),ResponseCode.ERROR.getDesc());
    }


    public static  ServerResponse createByErrorMessage(String errorMessage){
 return new ServerResponse(ResponseCode.ERROR.getCode(),errorMessage);
    }

    public static  ServerResponse createByErrorCodeMessage(int errorCode,String errorMessage){
 return new ServerResponse(errorCode,errorMessage);
    }

}


ResponseCode.java

package com.mmall.common;


public enum ResponseCode {

    SUCCESS(0,"SUCCESS"),
    ERROR(1,"ERROR"),
    NEED_LOGIN(10,"NEED_LOGIN"),
    ILLEGAL_ARGUMENT(2,"ILLEGAL_ARGUMENT");

    private final int code;
    private final String desc;


    ResponseCode(int code,String desc){
 this.code = code;
 this.desc = desc;
    }

    public int getCode(){
 return code;
    }
    public String getDesc(){
 return desc;
    }

}


分类模块 递归算法
    
    public ServerResponse> selectCategoryAndChildrenById(Integer categoryId){
 Set categorySet = Sets.newHashSet();
 findChildCategory(categorySet,categoryId);


 List categoryIdList = Lists.newArrayList();
 if(categoryId != null){
     for(Category categoryItem : categorySet){
  categoryIdList.add(categoryItem.getId());
     }
 }
 return ServerResponse.createBySuccess(categoryIdList);
    }


    //递归算法,算出子节点
    private Set findChildCategory(Set categorySet ,Integer categoryId){
 Category category = categoryMapper.selectByPrimaryKey(categoryId);
 if(category != null){
     categorySet.add(category);
 }
 //查找子节点,递归算法一定要有一个退出的条件
 List categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId);
 for(Category categoryItem : categoryList){
     findChildCategory(categorySet,categoryItem.getId());
 }
 return categorySet;
    }


复杂对象排重

Set集合

重写自定义对象Category的equals和hashCode方法

无限层级树结构设计

商品模块 POJO、BO、VO抽象模型

Product.java

package com.mmall.pojo;

import java.math.BigDecimal;
import java.util.Date;

public class Product {
    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 Date createTime;

    private Date updateTime;

    public Product(Integer id, Integer categoryId, String name, String subtitle, String mainImage, String subImages, String detail, BigDecimal price, Integer stock, Integer status, Date createTime, Date updateTime) {
 this.id = id;
 this.categoryId = categoryId;
 this.name = name;
 this.subtitle = subtitle;
 this.mainImage = mainImage;
 this.subImages = subImages;
 this.detail = detail;
 this.price = price;
 this.stock = stock;
 this.status = status;
 this.createTime = createTime;
 this.updateTime = updateTime;
    }

    public Product() {
 super();
    }

    public Integer getId() {
 return id;
    }

    public void setId(Integer id) {
 this.id = id;
    }

    public Integer getCategoryId() {
 return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
 this.categoryId = categoryId;
    }

    public String getName() {
 return name;
    }

    public void setName(String name) {
 this.name = name == null ? null : name.trim();
    }

    public String getSubtitle() {
 return subtitle;
    }

    public void setSubtitle(String subtitle) {
 this.subtitle = subtitle == null ? null : subtitle.trim();
    }

    public String getMainImage() {
 return mainImage;
    }

    public void setMainImage(String mainImage) {
 this.mainImage = mainImage == null ? null : mainImage.trim();
    }

    public String getSubImages() {
 return subImages;
    }

    public void setSubImages(String subImages) {
 this.subImages = subImages == null ? null : subImages.trim();
    }

    public String getDetail() {
 return detail;
    }

    public void setDetail(String detail) {
 this.detail = detail == null ? null : detail.trim();
    }

    public BigDecimal getPrice() {
 return price;
    }

    public void setPrice(BigDecimal price) {
 this.price = price;
    }

    public Integer getStock() {
 return stock;
    }

    public void setStock(Integer stock) {
 this.stock = stock;
    }

    public Integer getStatus() {
 return status;
    }

    public void setStatus(Integer status) {
 this.status = status;
    }

    public Date getCreateTime() {
 return createTime;
    }

    public void setCreateTime(Date createTime) {
 this.createTime = createTime;
    }

    public Date getUpdateTime() {
 return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
 this.updateTime = updateTime;
    }
}


ProductDetailVo.java

package com.mmall.vo;

import java.math.BigDecimal;


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;


    private String imageHost;
    private Integer parentCategoryId;

    public Integer getId() {
 return id;
    }

    public void setId(Integer id) {
 this.id = id;
    }

    public Integer getCategoryId() {
 return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
 this.categoryId = categoryId;
    }

    public String getName() {
 return name;
    }

    public void setName(String name) {
 this.name = name;
    }

    public String getSubtitle() {
 return subtitle;
    }

    public void setSubtitle(String subtitle) {
 this.subtitle = subtitle;
    }

    public String getMainImage() {
 return mainImage;
    }

    public void setMainImage(String mainImage) {
 this.mainImage = mainImage;
    }

    public String getSubImages() {
 return subImages;
    }

    public void setSubImages(String subImages) {
 this.subImages = subImages;
    }

    public String getDetail() {
 return detail;
    }

    public void setDetail(String detail) {
 this.detail = detail;
    }

    public BigDecimal getPrice() {
 return price;
    }

    public void setPrice(BigDecimal price) {
 this.price = price;
    }

    public Integer getStock() {
 return stock;
    }

    public void setStock(Integer stock) {
 this.stock = stock;
    }

    public Integer getStatus() {
 return status;
    }

    public void setStatus(Integer status) {
 this.status = status;
    }

    public String getCreateTime() {
 return createTime;
    }

    public void setCreateTime(String createTime) {
 this.createTime = createTime;
    }

    public String getUpdateTime() {
 return updateTime;
    }

    public void setUpdateTime(String updateTime) {
 this.updateTime = updateTime;
    }

    public String getImageHost() {
 return imageHost;
    }

    public void setImageHost(String imageHost) {
 this.imageHost = imageHost;
    }

    public Integer getParentCategoryId() {
 return parentCategoryId;
    }

    public void setParentCategoryId(Integer parentCategoryId) {
 this.parentCategoryId = parentCategoryId;
    }
}


静态代码块>普通代码块>构造代码块

Tomcat启动加载静态代码块

时间转换工具类DateTimeUtil.java
package com.mmall.util;

import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.util.Date;


public class DateTimeUtil {

    //joda-time

    //str->Date
    //Date->str
    public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";



    public static Date strToDate(String dateTimeStr,String formatStr){
 DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr);
 DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
 return dateTime.toDate();
    }

    public static String dateToStr(Date date,String formatStr){
 if(date == null){
     return StringUtils.EMPTY;
 }
 DateTime dateTime = new DateTime(date);
 return dateTime.toString(formatStr);
    }

    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"));

    }


}



mybatis-pagehelper高效分页

配置pom.xml

实现

mybatis-pagehelper动态排序

mybatis对List遍历的实现方法

mybatis对where语句动态拼装

FTP服务对接

先将文件上传到本地======>上传到远程ftp====>删除本地文件

配置pom.xml

  
    
    
      commons-fileupload
      commons-fileupload
      1.2.2
    

    
      commons-io
      commons-io
      2.0.1
    


配置dispatcher-servlet.xml



     
    
    


FileServiceImpl.java

package com.mmall.service.impl;

import com.google.common.collect.Lists;
import com.mmall.service.IFileService;
import com.mmall.util.FTPUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;


@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();
 //扩展名
 //abc.jpg
 String fileExtensionName = fileName.substring(fileName.lastIndexOf(".")+1);
 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);


 try {
     file.transferTo(targetFile);
     //文件已经上传成功了


     FTPUtil.uploadFile(Lists.newArrayList(targetFile));
     //已经上传到ftp服务器上

     targetFile.delete();
 } catch (IOException e) {
     logger.error("上传文件异常",e);
     return null;
 }
 //A:abc.jpg
 //B:abc.jpg
 return targetFile.getName();
    }

}



连接远程ftp,上传文件

FTPUtil.java

package com.mmall.util;

import org.apache.commons.net.ftp.FTPClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;


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 fileList) throws IOException {
 FTPUtil ftpUtil = new FTPUtil(ftpIp,21,ftpUser,ftpPass);
 logger.info("开始连接ftp服务器");
 boolean result = ftpUtil.uploadFile("img",fileList);
 logger.info("开始连接ftp服务器,结束上传,上传结果:{}");
 return result;
    }


    private boolean uploadFile(String remotePath,List fileList) throws IOException {
 boolean uploaded = true;
 FileInputStream fis = null;
 //连接FTP服务器
 if(connectServer(this.ip,this.port,this.user,this.pwd)){
     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;
    }


    private String ip;
    private int port;
    private String user;
    private String pwd;
    private FTPClient ftpClient;

    public String getIp() {
 return ip;
    }

    public void setIp(String ip) {
 this.ip = ip;
    }

    public int getPort() {
 return port;
    }

    public void setPort(int port) {
 this.port = port;
    }

    public String getUser() {
 return user;
    }

    public void setUser(String user) {
 this.user = user;
    }

    public String getPwd() {
 return pwd;
    }

    public void setPwd(String pwd) {
 this.pwd = pwd;
    }

    public FTPClient getFtpClient() {
 return ftpClient;
    }

    public void setFtpClient(FTPClient ftpClient) {
 this.ftpClient = ftpClient;
    }
}



富文本上传

和ftp文件上传类似,只是对返回值有特殊要求

购物车模块 商品总价计算复用封装
package com.mmall.util;

import java.math.BigDecimal;


public class BigDecimalUtil {

    private BigDecimalUtil(){

    }


    public static BigDecimal add(double v1,double v2){
 BigDecimal b1 = new BigDecimal(Double.toString(v1));
 BigDecimal b2 = new BigDecimal(Double.toString(v2));
 return b1.add(b2);
    }

    public static BigDecimal sub(double v1,double v2){
 BigDecimal b1 = new BigDecimal(Double.toString(v1));
 BigDecimal b2 = new BigDecimal(Double.toString(v2));
 return b1.subtract(b2);
    }


    public static BigDecimal mul(double v1,double v2){
 BigDecimal b1 = new BigDecimal(Double.toString(v1));
 BigDecimal b2 = new BigDecimal(Double.toString(v2));
 return b1.multiply(b2);
    }

    public static BigDecimal div(double v1,double v2){
 BigDecimal b1 = new BigDecimal(Double.toString(v1));
 BigDecimal b2 = new BigDecimal(Double.toString(v2));
 return b1.divide(b2,2,BigDecimal.ROUND_HALF_UP);//四舍五入,保留2位小数

 //除不尽的情况
    }

}



高复用的逻辑方法封装思想

解决商业运算丢失精度的坑

一定要用BigDecimal的String构造函数

public class BigDecimalTest {

    @Test
    public void test1(){
 System.out.println(0.05+0.01);
 System.out.println(1.0-0.42);
 System.out.println(4.015*100);
 System.out.println(123.3/100);
    }


    @Test
    public void test2(){
 BigDecimal b1 = new BigDecimal(0.05);
 BigDecimal b2 = new BigDecimal(0.01);
 System.out.println(b1.add(b2));
    }

    @Test
    public void test3(){
 BigDecimal b1 = new BigDecimal("0.05");
 BigDecimal b2 = new BigDecimal("0.01");
 System.out.println(b1.add(b2));

    }

}


订单模块 安全漏洞解决方案 订单号生成规则

强大的常量、枚举设计

Const.java

package com.mmall.common;

import com.google.common.collect.Sets;

import java.util.Set;


public class Const {

    public static final String CURRENT_USER = "currentUser";

    public static final String EMAIL = "email";
    public static final String USERNAME = "username";

    public interface ProductListOrderBy{
 Set PRICE_ASC_DESC = Sets.newHashSet("price_desc","price_asc");
    }

    public interface Cart{
 int CHECKED = 1;//即购物车选中状态
 int UN_CHECKED = 0;//购物车中未选中状态

 String LIMIT_NUM_FAIL = "LIMIT_NUM_FAIL";
 String LIMIT_NUM_SUCCESS = "LIMIT_NUM_SUCCESS";
    }

    public interface Role{
 int ROLE_CUSTOMER = 0; //普通用户
 int ROLE_ADMIN = 1;//管理员
    }

    public enum ProductStatusEnum{
 ON_SALE(1,"在线");
 private String value;
 private int code;
 ProductStatusEnum(int code,String value){
     this.code = code;
     this.value = value;
 }

 public String getValue() {
     return value;
 }

 public int getCode() {
     return code;
 }
    }


    public enum OrderStatusEnum{
 CANCELED(0,"已取消"),
 NO_PAY(10,"未支付"),
 PAID(20,"已付款"),
 SHIPPED(40,"已发货"),
 ORDER_SUCCESS(50,"订单完成"),
 ORDER_CLOSE(60,"订单关闭");


 OrderStatusEnum(int code,String value){
     this.code = code;
     this.value = value;
 }
 private String value;
 private int code;

 public String getValue() {
     return value;
 }

 public int getCode() {
     return code;
 }

 public static OrderStatusEnum codeOf(int code){
     for(OrderStatusEnum orderStatusEnum : values()){
  if(orderStatusEnum.getCode() == code){
      return orderStatusEnum;
  }
     }
     throw new RuntimeException("么有找到对应的枚举");
 }
    }
    public interface  AlipayCallback{
 String TRADE_STATUS_WAIT_BUYER_PAY = "WAIT_BUYER_PAY";
 String TRADE_STATUS_TRADE_SUCCESS = "TRADE_SUCCESS";

 String RESPONSE_SUCCESS = "success";
 String RESPONSE_FAILED = "failed";
    }



    public enum PayPlatformEnum{
 ALIPAY(1,"支付宝");

 PayPlatformEnum(int code,String value){
     this.code = code;
     this.value = value;
 }
 private String value;
 private int code;

 public String getValue() {
     return value;
 }

 public int getCode() {
     return code;
 }
    }

    public enum PaymentTypeEnum{
 ONLINE_PAY(1,"在线支付");

 PaymentTypeEnum(int code,String value){
     this.code = code;
     this.value = value;
 }
 private String value;
 private int code;

 public String getValue() {
     return value;
 }

 public int getCode() {
     return code;
 }


 public static PaymentTypeEnum codeOf(int code){
     for(PaymentTypeEnum paymentTypeEnum : values()){
  if(paymentTypeEnum.getCode() == code){
      return paymentTypeEnum;
  }
     }
     throw new RuntimeException("么有找到对应的枚举");
 }

    }

}


使用


mybatis批量插入

收货地址 同步获取自增主键

数据绑定的对象绑定

收货地址对象必须要有get和set方法

越权问题升级巩固 支付模块 支付宝SDK源码解析

当面付

当面付Demo

服务端SDK下载和使用教程

沙箱环境使用

生成RSA密钥

支付宝支付流程与集成

导入依赖

配置maven插件,除了pom.xml配置的依赖,lib包中的依赖也会一起打包部署

配置沙箱环境zfbinfo.properties

# 支付宝网关名、partnerId和appId
open_api_domain = https://openapi.alipaydev.com/gateway.do
mcloud_api_domain = http://mcloudmonitor.com/gateway.do
pid = 2088102180444972
appid = 2016102100728313


# RSA私钥、公钥和支付宝公钥
private_key = MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCKQ2pEIzXM4IoN1cMzOFXdlk8yVX2cKXWITZ92EGAQQcRytaV07yQOaz3UE9KTeT9Nu628G+HZMsJUxQjEUETagmY5nLtbeL35M2UcibYpM3e2gVTtUW86CA65GCdLzUhdIug8yf2F9zWayzG4sHZ9DcTezG6ZjFu+EtDpFgg+CtqY7n/ihjTIqeE1lX0C2ZIKpIYs7QjR8AztB/qRcpOJKRfMKGDgmT9GALN8LeFEYCbQ+W/GJHN8bQ0Bk1Ll6EKQ4cHXZ1Yko+aXaRfbXfUZYgD9hwAVlxtwZndgeFX8KapOCw0J25pzV4WkutIjMlt7I2Q1jaWNoKLuxtz4M2mzAgMBAAECggEAAhnsL4TpeGehMXyiLtEYXPm/0mACPLFUm/GyDrVJAHY/ag7gqNpJjf6LPgHfHWamU6Qai9VQpWBkG62y6Gjf4wJAU3fSUR2QpYzmaHyfTBkAJMHqbIDkU9lzf9SiJEDGbMPvC512QOb05ZlY9Bmac2QWLdylgafkbQsUKbawAWFa/BAOMIp0tgYLW8/yY2aG6jeLqhOgTo8MWIW5d1qHtX5m/x7g97dYYMdX3kTo2i1dFLUVfEOvZe4US6VBvLg71dMxwadVF5YMaY9jq/ShPD0Gkf29wdThwsjcH6u9Tq/KArQTK+z02DAGkdWOcue3pHql+gvoIA8U5uFDdIeYwQKBgQDri3jPkDKi48efdKQk38rn+CJYeNFNRAhlly3h2AHaFEY92XRlBsho/vGFg43BvHu+cMz0Run4SS8Vo09vcTIY6p2xNMffjR0w2gQqx6PUdGHBFtw7FavxN4uVtVhL6uTAqfBv97mqQO0bq+DhOGwSRNIWqvnzfXywqwmXhKYECwKBgQCWRTl6tNv8scxPq4fpRL/uw71TU6XqSS/nME7KT4uyQPAXPk0mXVVwdmyST9Crlr6O6WJopPe9nMIFUYdjdkLfGKLCR96AH3U7frr4jf60eDYEhfHGIzln/ptrTJLvvbXTaPctAaZd6TIv63QVz3yim4MMl3VSdRlrE+O9R5ZR+QKBgQDjEP8TyUSnNsJX+4/JZFwsp04kz8OlorIdjVHT5/JREz5rnVfRlGpanXqjZSCg5Vy9R+ysiDhA+/wB9f87xXmv/2ypSeJspZLAZ0uhGffbdZ5PEASaiNfKn+tWFQ3bkcOX37tDlSJM+G4bQOR2+XdlXSbSZ1yx2AT+IsQKZvvL5QKBgQCPZEUiEz0sV1kX2R2a+XCQ3RVnUxWqh+X/HPjCUr+B/DdeZqPl7QAfjdGymBkN842o/4lZQ7nnpJL70j14KpxLGM4Ox9fIuLv8ZsTxc0XOXjtle48nO+uGkc0qyWoY/RVpQ+tBdiaTzHeIhIxEV7adz/lwZYKdiYIUzGjv8ES/uQKBgCgeWysXjahCQItxx5fTrS8SQFP7Dx5vDW+UkqQ2pbL0AlHyUS7pWJj3AAe3pn4AJZZp4SZPoQP+Z8JPqDA6MrQWHYMi0XkMuMYwLWbGCkmf1MnjUxgOaLXoItjxS/y3jQfeOmHhmOAVkjnEvAh+BWlZxFMv2kiuHRU72bNa0rDI

public_key = MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAikNqRCM1zOCKDdXDMzhV3ZZPMlV9nCl1iE2fdhBgEEHEcrWldO8kDms91BPSk3k/TbutvBvh2TLCVMUIxFBE2oJmOZy7W3i9+TNlHIm2KTN3toFU7VFvOggOuRgnS81IXSLoPMn9hfc1mssxuLB2fQ3E3sxumYxbvhLQ6RYIPgramO5/4oY0yKnhNZV9AtmSCqSGLO0I0fAM7Qf6kXKTiSkXzChg4Jk/RgCzfC3hRGAm0PlvxiRzfG0NAZNS5ehCkOHB12dWJKPml2kX2131GWIA/YcAFZcbcGZ3YHhV/CmqTgsNCduac1eFpLrSIzJbeyNkNY2ljaCi7sbc+DNpswIDAQAB



#SHA1withRsa对应支付宝公钥
#alipay_public_key = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDI6d306Q8fIfCOaTXyiUeJHkrIvYISRcc73s3vF1ZT7XN8RNPwJxo8pWaJMmvyTn9N4HQ632qJBVHf8sxHi/fEsraprwCtzvzQETrNRwVxLO5jVmRGi60j8Ue1efIlzPXV9je9mkjzOmdssymZkh2QhUrCmZYI/FCEa3/cNMW0QIDAQAB

#SHA256withRsa对应支付宝公钥
alipay_public_key = MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqzWgVL/NWrJAeyEImwtaK3IDwj0dKkqUDIfqqWn5SiLaWMYi9RmKhn+jY9VM7JXEIkYYeVlqIL6Xn7OvYFRTi4buTCXGKvFLn95aDcaur77/S/0ibcdN1K2wIoHzaqQhXAb1ezKxTnFP7OLJsAL22b0NzrQDj2OH9SA06gJb8nHBfR+7Sx7DfwcqE0OtTcDHjbbcB24Qgg/dfItxoEnKuSyRVrf6BtpUnJxSzG/Ge7FfF+VBq8re1t4ZTSxaDEjto071I5VFBxr7I4SyqZsc7WpAmZL8AqUgEbQ1XYBWx2LnpJXM5GQW/thUvcDDqzea7LJNWJOQPM5DaZQgu7QuuwIDAQAB


# 签名类型: RSA->SHA1withRsa,RSA2->SHA256withRsa
sign_type = RSA2

# 当面付最大查询次数和查询间隔(毫秒)
max_query_retry = 5
query_duration = 5000

# 当面付最大撤销次数和撤销间隔(毫秒)
max_cancel_retry = 3
cancel_duration = 2000

# 交易保障线程第一次调度延迟和调度间隔(秒)
heartbeat_delay = 5
heartbeat_duration = 900


二维码生成,扫码支付

配置回调url

两次回调,扫码进行一次回调,扫码付款成功进行一次回调

生成二维码,上传到ftp服务器

OrderServiceImpl.java

package com.mmall.service.impl;


@Service("iOrderService")
public class OrderServiceImpl implements IOrderService {


    private static  AlipayTradeService tradeService;
    static {

 
 Configs.init("zfbinfo.properties");

 
 tradeService = new AlipayTradeServiceImpl.ClientBuilder().build();
    }

    private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderItemMapper orderItemMapper;
    @Autowired
    private PayInfoMapper payInfoMapper;
    @Autowired
    private CartMapper cartMapper;
    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private ShippingMapper shippingMapper;



    public ServerResponse pay(Long orderNo,Integer userId,String path){
 Map resultMap = Maps.newHashMap();
 Order order = orderMapper.selectByUserIdAndOrderNo(userId,orderNo);
 if(order == null){
     return ServerResponse.createByErrorMessage("用户没有该订单");
 }
 resultMap.put("orderNo",String.valueOf(order.getOrderNo()));



 // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
 // 需保证商户系统端不能重复,建议通过数据库sequence生成,
 String outTradeNo = order.getOrderNo().toString();


 // (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
 String subject = new StringBuilder().append("happymmall扫码支付,订单号:").append(outTradeNo).toString();


 // (必填) 订单总金额,单位为元,不能超过1亿元
 // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
 String totalAmount = order.getPayment().toString();


 // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
 // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
 String undiscountableAmount = "0";



 // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
 // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
 String sellerId = "";

 // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
 String body = new StringBuilder().append("订单").append(outTradeNo).append("购买商品共").append(totalAmount).append("元").toString();


 // 商户操作员编号,添加此参数可以为商户操作员做销售统计
 String operatorId = "test_operator_id";

 // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
 String storeId = "test_store_id";

 // 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
 ExtendParams extendParams = new ExtendParams();
 extendParams.setSysServiceProviderId("2088100200300400500");




 // 支付超时,定义为120分钟
 String timeoutExpress = "120m";

 // 商品明细列表,需填写购买商品详细信息,
 List goodsDetailList = new ArrayList();

 List orderItemList = orderItemMapper.getByOrderNoUserId(orderNo,userId);
 for(OrderItem orderItem : orderItemList){
     GoodsDetail goods = GoodsDetail.newInstance(orderItem.getProductId().toString(), orderItem.getProductName(),
      BigDecimalUtil.mul(orderItem.getCurrentUnitPrice().doublevalue(),new Double(100).doublevalue()).longValue(),
      orderItem.getQuantity());
     goodsDetailList.add(goods);
 }

 // 创建扫码支付请求builder,设置请求参数
 AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
  .setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo)
  .setUndiscountableAmount(undiscountableAmount).setSellerId(sellerId).setBody(body)
  .setOperatorId(operatorId).setStoreId(storeId).setExtendParams(extendParams)
  .setTimeoutExpress(timeoutExpress)
  .setNotifyUrl(PropertiesUtil.getProperty("alipay.callback.url"))//支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
  .setGoodsDetailList(goodsDetailList);


 AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
 switch (result.getTradeStatus()) {
     case SUCCESS:
  logger.info("支付宝预下单成功: )");

  AlipayTradePrecreateResponse response = result.getResponse();
  dumpResponse(response);

  File folder = new File(path);
  if(!folder.exists()){
      folder.setWritable(true);
      folder.mkdirs();
  }

  // 需要修改为运行机器上的路径
  //细节细节细节
  String qrPath = String.format(path+"/qr-%s.png",response.getOutTradeNo());
  String qrFileName = String.format("qr-%s.png",response.getOutTradeNo());
  ZxingUtils.getQRCodeImge(response.getQrCode(), 256, qrPath);

  File targetFile = new File(path,qrFileName);
  try {
      FTPUtil.uploadFile(Lists.newArrayList(targetFile));
  } catch (IOException e) {
      logger.error("上传二维码异常",e);
  }
  logger.info("qrPath:" + qrPath);
  String qrUrl = PropertiesUtil.getProperty("ftp.server.http.prefix")+targetFile.getName();
  resultMap.put("qrUrl",qrUrl);
  return ServerResponse.createBySuccess(resultMap);
     case FAILED:
  logger.error("支付宝预下单失败!!!");
  return ServerResponse.createByErrorMessage("支付宝预下单失败!!!");

     case UNKNOWN:
  logger.error("系统异常,预下单状态未知!!!");
  return ServerResponse.createByErrorMessage("系统异常,预下单状态未知!!!");

     default:
  logger.error("不支持的交易状态,交易返回异常!!!");
  return ServerResponse.createByErrorMessage("不支持的交易状态,交易返回异常!!!");
 }

    }

    // 简单打印应答
    private void dumpResponse(AlipayResponse response) {
 if (response != null) {
     logger.info(String.format("code:%s, msg:%s", response.getCode(), response.getMsg()));
     if (StringUtils.isNotEmpty(response.getSubCode())) {
  logger.info(String.format("subCode:%s, subMsg:%s", response.getSubCode(),
   response.getSubMsg()));
     }
     logger.info("body:" + response.getBody());
 }
    }


    public ServerResponse aliCallback(Map params){
 Long orderNo = Long.parseLong(params.get("out_trade_no"));
 String tradeNo = params.get("trade_no");
 String tradeStatus = params.get("trade_status");
 Order order = orderMapper.selectByOrderNo(orderNo);
 if(order == null){
     return ServerResponse.createByErrorMessage("非快乐慕商城的订单,回调忽略");
 }
 if(order.getStatus() >= Const.OrderStatusEnum.PAID.getCode()){
     return ServerResponse.createBySuccess("支付宝重复调用");
 }
 if(Const.AlipayCallback.TRADE_STATUS_TRADE_SUCCESS.equals(tradeStatus)){
     order.setPaymentTime(DateTimeUtil.strToDate(params.get("gmt_payment")));
     order.setStatus(Const.OrderStatusEnum.PAID.getCode());
     orderMapper.updateByPrimaryKeySelective(order);
 }

 PayInfo payInfo = new PayInfo();
 payInfo.setUserId(order.getUserId());
 payInfo.setOrderNo(order.getOrderNo());
 payInfo.setPayPlatform(Const.PayPlatformEnum.ALIPAY.getCode());
 payInfo.setPlatformNumber(tradeNo);
 payInfo.setPlatformStatus(tradeStatus);

 payInfoMapper.insert(payInfo);

 return ServerResponse.createBySuccess();
    }


    public ServerResponse queryOrderPayStatus(Integer userId,Long orderNo){
 Order order = orderMapper.selectByUserIdAndOrderNo(userId,orderNo);
 if(order == null){
     return ServerResponse.createByErrorMessage("用户没有该订单");
 }
 if(order.getStatus() >= Const.OrderStatusEnum.PAID.getCode()){
     return ServerResponse.createBySuccess();
 }
 return ServerResponse.createByError();
    }

}




内网穿透

NATAPP1分钟快速新手图文教程

线上部署 云服务器vsftpd、nginx等配置 云服务器的配置与域名解析 发布上线注意事项
转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号