首先,恭喜本身(僞)獨立完成了一個包括後臺管理和前端商城的(簡陋至極的)商城系統。
在這一過程當中,我最主要的問題是——呃,什麼都不會!
沒錯,真的是什麼都不會。一個月前,我所擁有的對這個項目惟一有幫助的基礎是:使用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操做,網上的資料實在太多了,很是簡單也很容易掌握。而後這裏的登陸註冊涉及到了異常捕獲和入參校驗,這兩部分你要以爲麻煩徹底能夠不要,直接寫登陸註冊邏輯,可是這樣的設計是很不完善的。這兩部份內容網上一樣有不少資料,我將再也不贅述。