首先,恭喜本身(伪)独立完成了一个包括后台管理和前端商城的(简陋至极的)商城系统。
在这一过程当中,我最主要的问题是——呃,什么都不会!
没错,真的是什么都不会。一个月前,我所拥有的对这个项目惟一有帮助的基础是:使用eclipse自学过两周java编程算法。除此以外,没有任何web项目经验,没有任何spring经验,没有任何J2EE经验。在学校里只学过c++,并且仅限于算法。
老实说,当我面对这个项目命题时,我感到了深深的绝望和对以往荒废时光的可惜。因而,我开始作的第一件事就是,登陆b站,打开点击最高的spring,springmvc,mybatis,springboot视频。。。。。
咳咳,闲话扯远了,回到正题!
这篇博客我将尽量完整地展现一个springboot商城系统的构建思路和业务逻辑。固然,项目还存在一些不够友好的bug,以及功能单薄的缺点(可我实在懒得改了)。博客的发布,最主要目的是我对过去一个月的思考和知识的巩固汇总,因此不会一一列举全部的项目内容(好比对对象的增删改查操做等),尤为是我本身以为很简单的部分,因此可能会显得不够全面,若是你须要全面的代码,那么能够去git上搜索靠前的原码。
那么问题来了,依靠这篇博客你能够成功完成一个项目吗?
答案显然是no。由于我只是总结一些我的在项目中遇到的重点,我本身自己也是经过视频,博客,git等途径逐步学习完成的整个项目。也就是说,这不是一篇教学类的博客。
若是你要完成一个本身的项目,你须要看更多的视频,学更多的基础知识,看更多的博客,这只是一个参考,而且不必定是你能用到的参考。仅此。
基原本说,我将采起天天(???)更新的方式,逐步完成。css
由于初学者,因此项目使用的都是最基础的技术框架。以下:
html
(懒得手敲,直接ppt截图)
其中springboot括号里的组件是我在完成基本功能后,经过度娘添加补充的。前端
这是项目开始之初,首先要作的工做。固然,我这一步作的就很差,直到项目差很少了,我才整理好需求分析,能够说是很不专业了。java
1注册与登陆 2用户我的信息(包括地址和积分信息)的查看与修改 3用户购物车的查看与修改 4我的订单的查看、取消、下单 5积分兑换商品记录查询
1商品展现 2商品分类及展现 3商品详情 4商品加入购物车以及下单
1商品下单 2支付订单以及退单
1积分商城 2积分获取规则 3积分兑换商品
1用户信息搜索查询 2用户信息增删改 3地址信息搜索查询 4用户地址信息增删改
1商城商品增删改查 2积分商品增删改查 3商品类别增删改查
1订单的分类查询 2订单的删改查
1用户积分查询与修改 2积分兑换记录查询
能够看见,思惟导图就是在需求分析后整理成可视化的图形结构,使得整个项目功能一目了然,同时还可使用标记来记录本身的完成状况,就很舒服。一样地固然,我这一步作的也不太好,开始时列举功能不够详细,思路不清晰,也能够看出,个人功能至关单薄(因此说,菜就是原罪啊!)。因此,思惟导图的列举,必定要具体详细,每完成一项就作一个标记,成就感满满!mysql
这一大模块,我将采用本身完成项目的时间步骤书写。基原本说,就是管理平台后端——管理平台前端——商城后端——商城前端的顺序。其中,前端部分我花了五分之三还多的时间,后端花费时间较少(前端实在是不会啊!!!)。因为不少内容是简单的复用(好比后台对用户,商品的管理操做),因此不会所有列举。c++
数据库表
git
项目结构
web
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
username: ######
password: ######
url: jdbc:mysql://localhost:3306/miaosha?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver算法
jackson:
default-property-inclusion: NON_NULL
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
mvc:
view:
suffix: .html
prefix: /
#resources:
#classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
#static-locations: classpath:/css/, classpath:/image/, classpath:/js/
#static-path-pattern: /static/*spring
thymeleaf:
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
cache: false
#配置驼峰映射
configuration:
map-underscore-to-camel-case: true
mybatis:
typeAliasesPackage: com.pf.businessdemo.dataobject
mapperLocations: classpath:mapping/*.xml
configuration:
map-underscore-to-camel-case: true
(这里是一个最简单的配置,学过一点基础的想必都懂。)
除此以外的依赖注入我也再也不列举,都是一个web项目所必需的jar包。
public class UserDo {
private Integer id;
private String name; private String gender; private Integer age; private String telphone; private String registerMode; private String thirdPartyId; private String receiverAddress; private Integer integral;
getset省略
public interface UserService{
/**
@Description: 经过id获取用户信息
@Param: id
@return: usermodel用户领域模型
/
UserModel getUserById(Integer id);
/** *@Description:经过id删除用户信息 *@Param: id *@return: void */ void deleteUser(Integer id); /** *@Description:经过name删除用户 *@Param:userdo *@return: */ int deleteByName(UserDo userDo ); /** *@Description:经过id更改用户信息 *@Param: 要更改的用户信息 *@return: void */ void updateUserInfo(UserDo userDo); /** *@Description:完善用户信息 *@Param:用户填写的具体信息 *@return: */ UserDo insertUserInfo(UserDo userDo); /** *@Description:查询获取全部用户信息 *@Param: *@return:list<userdo> */ List<UserDo> findUserAll(); /** *@Description:联合查询用户及密码信息 *@Param: *@return: */ List<UserPassWordDo> findDouble(); /** *@Description:经过名字搜索用户 *@Param:用户信息 *@return: */ List<UserDo> findUserByName(UserDo userDo); /** *@Description: 用户注册接口 *@Param: 用户领域模型 *@return: */ void register(UserModel userModel)throws BusinessException; /** *@Description:用户带校验的登录接口 *@Param: 用户手机,用户加密密码 *@return: */ UserModel validateLogin(String telphone,String encrptPassword) throws BusinessException;
这里列举了全部的用户service接口,目前只须要关注登陆注册相关的便可。
@Override
public List
return userDoMapper.findUserByName(userDo);
}
@Override @Transactional public void register(UserModel userModel) throws BusinessException { if (userModel==null){ throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR); } ValidationResult result=validator.validate(userModel); if (result.isHasError()){ throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,result.getErrMsg()); } //实现model->dataobject方法 UserDo userDo=convertFromDataObject(userModel); try { userDoMapper.insertSelective(userDo); }catch (DuplicateKeyException ex){ throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"手机号已重复注册"); } userModel.setId(userDo.getId()); UserPassWordDo userPassWordDo=convertPasswordFromDataObject(userModel); userpasswordDoMapper.insertSelective(userPassWordDo); return; } @Override public UserModel validateLogin(String telphone, String encrptPassword) throws BusinessException { //经过用户手机获取用户信息 UserDo userDo=userDoMapper.selectByTelphone(telphone); if (userDo==null){ throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL ); } UserPassWordDo userPassWordDo=userpasswordDoMapper.selectByUserId(userDo.getId()); UserModel userModel=convertFromDataObject(userDo,userPassWordDo); //比对用户信息内加密的密码是否和传输进来的密码相匹配 if (!StringUtils.equals(encrptPassword,userModel.getEncrptPassword())){ throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL ); } return userModel; } private UserPassWordDo convertPasswordFromDataObject(UserModel userModel){ if (userModel==null){ return null; } UserPassWordDo userPassWordDo=new UserPassWordDo(); userPassWordDo.setEncrptPassword(userModel.getEncrptPassword()); userPassWordDo.setUserId(userModel.getId()); return userPassWordDo; } private UserDo convertFromDataObject(UserModel userModel){ if (userModel==null){ return null; } UserDo userDo=new UserDo(); BeanUtils.copyProperties(userModel,userDo); return userDo; } private UserModel convertFromDataObject(UserDo userDo, UserPassWordDo userpasswordDo){ if(userDo==null){ return null; } UserModel userModel=new UserModel(); BeanUtils.copyProperties(userDo,userModel); if(userpasswordDo!=null){ userModel.setEncrptPassword(userpasswordDo.getEncrptPassword()); } return userModel; }
1首先是register(注册)方法:
在这里须要说明一下,个人用户表是不包含用户密码的,而是单独写了一张user_password表,以下
接着用一个用户领域模型UserModel来包含用户全部字段
public class UserModel {
private Integer id;
@NotBlank(message = "用户名不能为空")
private String name;
@NotNull(message = "性别为必填选项")
private String gender;
@NotNull(message = "年龄为必填选项")
@Min(value = 0,message = "年龄必须大于0岁")
@Max(value = 150,message = "年龄必须小于150岁")
private Integer age;
@NotNull(message = "手机号不能为空")
private String telphone;
private String registerMode;
private String thirdPartyId;
@NotNull(message = "密码不能为空")
private String encrptPassword;
@NotNull(message = "手机号不能为空")
private String receiverAddress;
private Integer integral;
getset省略
由于是对两张表操做,因此自定义了相似convertPasswordFromDataObject的方法,逻辑很是简单,用到了BeanUtils.copyProperties(a,b)(将a赋给b)。
对代码逐行分析。首先是对UserModel的判空处理,定义异常,使用ExceptionHandler捕获,接着是对注册信息的输入校验,这里使用了HibernateValidator。
接下来,调用用户的mapper接口UserDoMapper和密码的mapper接口UserPasswordDoMapper的insert方法,这里逻辑比较简单,再也不赘述。
2登陆validateLogin方法
登陆的逻辑是,首先经过用户手机号获得用户信息,接着对比用户加密密码是否和传输进来的密码相匹配。具体实现看代码便可。
/**
@Description:用户登陆接口
@Param:用户登陆信息
@return:
/
@RequestMapping("/index")
public String index() {
return "admin/adminHomepage";
}
@RequestMapping(value = "/login") @ResponseBody public CommonReturnType login(@RequestParam(name = "telphone") String telphone, @RequestParam(name = "password") String password) throws BusinessException, UnsupportedEncodingException, NoSuchAlgorithmException { //入参校验 if (org.apache.commons.lang3.StringUtils.isEmpty(telphone) || StringUtils.isEmpty(password)) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR); } //用户登陆服务,用来校验用户登陆是否合法 UserModel userModel = userService.validateLogin(telphone, this.EncodeByMd5(password)); //将登陆凭证加入到用户登录成功的session内 this.httpServletRequest.getSession().setAttribute("IS_LOGIN", true); this.httpServletRequest.getSession().setAttribute("LOGIN_USER", userModel); String url = "/shop/home"; return CommonReturnType.create(url); } /** *@Description:用户注册接口 *@Param:注册信息 *@return: */ @RequestMapping(value = "/register", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED}) @ResponseBody public CommonReturnType register(@RequestParam(name = "telphone") String telphone, @RequestParam(name = "otpCode") String otpCode, @RequestParam(name = "name") String name, @RequestParam(name = "gender") String gender, @RequestParam(name = "password") String password, @RequestParam(name = "age") Integer age, @RequestParam(name = "receiverAddress") String receiverAddress) throws BusinessException, UnsupportedEncodingException, NoSuchAlgorithmException { //验证手机号和对应的otpCode相符合 String inSessionOtpCode = (String) this.httpServletRequest.getSession().getAttribute(telphone); if (!com.alibaba.druid.util.StringUtils.equals(otpCode, inSessionOtpCode)) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "短信验证码不符合"); } //用户的注册流程 UserModel userModel = new UserModel(); userModel.setName(name); userModel.setGender(gender); userModel.setAge(age); userModel.setTelphone(telphone); userModel.setRegisterMode("byphone"); userModel.setEncrptPassword(this.EncodeByMd5(password)); userModel.setReceiverAddress(receiverAddress); userService.register(userModel); return CommonReturnType.create(null); }
1登陆controller
Controller里的代码写的很是清楚(最开始的index()方法能够忽略,这是我写的跳转后台首页),一样是入参校验(这里是仅是判空),接着调用service层的登陆方法,最后将登陆信息存入session,方便以后的状态控制。要特别说明的是最后的CommonReturnType类型,直接上代码:
private String status;
private Object data;
private String url;
/** *@Description:定义一个通用的建立方法 *@Param: *@return: */ public static CommonReturnType create(Object result){ return CommonReturnType.create(result ,"success"); } public static CommonReturnType create(String url){ return CommonReturnType.create(null,"success",url); } public static CommonReturnType create(Object result,String status){ CommonReturnType type=new CommonReturnType(); type.setStatus(status); type.setData(result); return type; } public static CommonReturnType create(Object result,String status,String url){ CommonReturnType type=new CommonReturnType(); type.setStatus(status); type.setData(result); type.setUrl(url); return type; }
getset省略
这个类放在前面的response包,表示一个通用的返回类型,主要由状态status,数据data构成(最后的属性url是由于个人登陆页面是静态页面,而我后来其余页面用的是模板页面写的,因此强行先后端不分离,若是你要写先后端分离,能够不加)。类里面定义了一个creat方法,若是入参为null则默认status为success,接着重写方法将入参填入便可。
2注册controller
使用@RequestParam注解将参数注入,接着验证手机号和对应的otpCode是否符合(otp短信验证码后面讲),最后set属性。这里涉及到Md5加密,我在同一个类里定义了一个EncodeByMd5方法,以下:
@ResponseBody
public String EncodeByMd5(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {
//肯定计算方法
MessageDigest md5 = MessageDigest.getInstance("MD5");
BASE64Encoder base64en = new BASE64Encoder();
//加密字符串
String newstr = base64en.encode(md5.digest(str.getBytes("utf-8")));
return newstr;
}
接下来是用户获取otp短信接口:
@RequestMapping(value = "/getotp", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
@ResponseBody
public CommonReturnType getOtp(@RequestParam(name = "telphone") String telphone) {
//须要按照必定的规则生成otp验证码
Random random = new Random();
int randomInt = random.nextInt(99999);
randomInt += 10000;
String otpCode = String.valueOf(randomInt);
//将otp验证码同对应用户的手机号关联,使用httpsession的方式绑定用户手机号与otpcCode
httpServletRequest.getSession().setAttribute(telphone, otpCode);
//将otp验证码经过短信通道发送给用户,省略
System.out.println("telphone = " + telphone + " &otpCode = " + otpCode);
return CommonReturnType.create(null);
}
我这里是将otp验证码打印到控制台,只为模拟短信验证流程。(这部份内容网上有不少,我也是照猫画虎copy的)
关于用户后台模块的其余内容我以为很没有必要写,由于用户后台模块都是一些简单的crud操做,网上的资料实在太多了,很是简单也很容易掌握。而后这里的登陆注册涉及到了异常捕获和入参校验,这两部分你要以为麻烦彻底能够不要,直接写登陆注册逻辑,可是这样的设计是很不完善的。这两部份内容网上一样有不少资料,我将再也不赘述。