??? ? 🧸安清h:個人主頁?
? ?🎥個人專欄:【Spring篇】【計算機網絡】【Mybatis篇】
🚦作者簡介:一個有趣愛睡覺的intp,期待和更多人分享自己所學知識的真誠大學生。
目錄
🚀1.上傳頭像 -持久層
?1.1規劃SQL語句
?1.2設計接口和抽象方法
?1.3 接口的映射
?1.4單元測試
🚀2.上傳頭像 -業務層
?2.1規劃異常
?2.2設計接口和抽象方法
?2.3抽象方法實現
🚀3.上傳頭像 -控制層
?3.1規劃異常
?3.2處理異常
?3.3設計請求
?3.4實現請求
🚀4.上傳頭像 -前端頁面
🚀5.解決BUG
?5.1更改默認的大小限制
?5.2顯示頭像
?5.3登錄后顯示頭像
🎃1.新增收貨地址-數據表創建
?🎃2.新增收貨地址-創建實體類
🎃3.新增收貨地址-持久層
?1.1各功能的開發順序
?1.2規劃需要執行的SQL語句
?1.3接口與抽象方法
?1.4配置SQL映射
🎃4.新增收貨地址-業務層
?4.1規劃異常
?4.2接口與抽象方法
?4.3實現抽象方法?
🎃5.新增收貨地址-控制層
?5.1處理異常
?5.2設計請求
?5.3處理請求
🎃6.新增收貨地址-前端頁面
🚀1.上傳頭像 -持久層
?1.1規劃SQL語句
將對應的文件保存在操作系統上,然后再把這個文件路徑給記錄下來,因為記錄路徑是非常方便和便捷的,將來如果要打開這個文件,可以依據這個路徑去找到這個文件。在數據庫中需要保存這個文件的路徑即可。將所有的靜態資源(圖片,文件,其他)放到某臺電腦上,再把這臺電腦作為一臺單獨的服務器使用。
對應的是一個更新用戶avatar字段的sql語句。
update t_user set avatar=?,modified_user=?,modified_time=?
where uid=?
?1.2設計接口和抽象方法
?UserMapper接口中來定義個抽象方法用于修改用戶的頭像。
/*** @Param("SQL映射文件中#{}占位符的變量名"):解決的問題,* 當SQL語句的占位符和映射接口方法參數名不一致時,需要將某個參數強行注入到某個占位符變量上時,* 可以使用@Param這個注解來標注映射關系*也就是說@Param("avatar")就相當于mapper.xml中的#{avatar}** 根據用戶的uid修改用戶的頭像* @param uid* @param avatar* @param modifiedUser* @param modifiedTime* @return 返回值為收影響的行數* */Integer updateAvatarByUid(@Param("uid") Integer uid,@Param("avatar") String avatar,@Param("modifiedUser") String modifiedUser,@Param("modifiedTime") Date modifiedTime);
?1.3 接口的映射
UserMapper.xml文件中編寫映射的SQL語句。
<update id="updateAvatarByUid">update t_user set avatar=#{avatar},modified_user=#{modifiedUser},modified_time=#{modifiedTime}where uid=#{uid}</update>
?1.4單元測試
在test->mapper包下的UserMapperTests類中編寫測試方法updateAvatarByUid,代碼如下:
@Testpublic void updateAvatarByUid(){userMapper.updateAvatarByUid(7,"/upload/avatar.png","管理員",new Date());}
🚀2.上傳頭像 -業務層
?2.1規劃異常
1.用戶數據不存在,找不到對應的用戶數據。
2.更新的時候,有未知異常的產生。
前面已開發完成,無需重新開發。?
?2.2設計接口和抽象方法
注釋的快捷方法:/**+enter
/*** 修改用戶的頭像* @param uid 用戶id* @param avatar 用戶的頭像* @param username 用戶的名稱*/void changeAvatar(Integer uid,String avatar,String username);
?2.3抽象方法實現
編寫業務層的更新頭像的方法。
@Overridepublic void changeAvatar(Integer uid, String avatar, String username) {User result = userMapper.findByUid(uid);if(result == null || result.getIsDelete() == 1){throw new UserNotFoundException("用戶數據不存在");}Integer rows = userMapper.updateAvatarByUid(uid,avatar, username, new Date());if(rows != 1){throw new UpdateException("更新時數據產生未知的異常");}}
測試業務層方法的執行。
@Testpublic void changeAvatar(){userService.changeAvatar(7,"/upload/test.png","haha");}
🚀3.上傳頭像 -控制層
?3.1規劃異常
文件異常的父類:
????????FileUploadException 泛指文件上傳的異常。(父類)繼承RuntimeException
父類是:FileUploadException
????????FileEmptyException 文件為空的異常。
????????FileSizeException 文件大小超出限制的異常。
????????FileStateException 文件狀態產生異常,即文件在打開狀態時無法上傳。
????????FileTypeException 文件類型異常。
????????FileUploadIOException 文件讀寫的異常。
?五個構造方法顯式聲明出來,再去繼承相關的父類。
在controller包下新建包ex,在ex包里新建以上六個類。其中父類FileUploadException繼承RuntimeException,其余五個類繼承父類FileUploadException,在此不再做過多代碼描述。
?3.2處理異常
在基類BaseController類中進行編寫和統一處理。
else if (e instanceof FileEmptyException) {result.setState(6000);} else if (e instanceof FileSizeException) {result.setState(6001);} else if (e instanceof FileTypeException) {result.setState(6002);} else if (e instanceof FileStateException) {result.setState(6003);} else if (e instanceof FileUploadIOException) {result.setState(6004);}
?在異常統一處理方法的參數列表上增加新的異常處理作為它的參數。
?3.3設計請求
請求路徑:/users/change_avatar
請求方式:POST(get請求提交數據2KB,不夠清晰)
請求數據:HttpSession session,MutipartFile file
響應結果:JsonResult<String>(記錄圖片的路徑,圖片路徑是一個串,即用String,如果不記錄下來,再切換到其他頁面就展示不出來了)
?3.4實現請求
MultipartFile接口是SpringMVC提供一個接口,這個接口為我們包裝了獲取文件類型的數據(任何類型的file都可以接受),SpringBoot它又整合了SpringMVC,只需要在處理請求的方法參數列表上聲明一個參數類型為MultipartFile,然后SpringBoot會自動將傳遞給服務的文件數據賦值給這個參數。
@RequestParam:表示請求中的參數,將請求中的參數注入請求處理方法的某個參數上,如果名稱不一致則可以使用@RequestParam注解進行標記和映射。
/**** @param session* @param file* @return*/@RequestMapping("change_avatar")public JsonResult<String> changeAvatar(HttpSession session,@RequestParam("file") MultipartFile file) {
// 判斷文件是否為空if(file.isEmpty()){throw new FileEmptyException("文件為空");}
// 判斷文件大小是否超出限制if(file.getSize() > AVATAR_MAX_SIZE){throw new FileSizeException("文件超出限制");}
// 判斷文件的類型是否為我們規定的和后綴類型String contentType = file.getContentType();if(!AVATAR_TYPE.contains(contentType)){throw new FileTypeException("文件類型不支持");}
// 上傳的文件.../upload/文件.pngString parent = session.getServletContext().getRealPath("upload");
// File對象指向這個路徑,File是否存在File dir = new File(parent);if(!dir.exists()){dir.mkdir(); //創建當前的目錄}
// 獲取到這個文件名稱,UUID工具來將生成一個新的字符串作為文件名
// 返回的只是文件名,不包含目錄結構.例如:avatar01.png。
// 在后續中,前面的avatar01即名稱部分可以根據uid來生成一個隨機的大小寫字符串保存下來,
// 后綴需要保存下來String originalFilename =file.getOriginalFilename();System.out.println("OriginalFilename"+originalFilename);int index = originalFilename.lastIndexOf("."); //查找字符串中最后一次出現的點號(.)的位置。String suffix = originalFilename.substring(index); //從字符串的指定索引位置開始截取字符串,直到字符串的末尾。String filename = UUID.randomUUID().toString().toUpperCase() + suffix;File dest = new File(dir,filename); //在dir下創建一個叫filename的文件,此時它是一個空文件
// 將參數file中的數據寫入到空文件當中try{file.transferTo(dest); //將file中的數據寫入到dest文件中}catch (FileStateException e){throw new FileStateException("文件狀態異常");}catch (IOException e){throw new FileUploadIOException("文件讀寫異常");}Integer uid = getuidFromSession(session);String username = getUsernameFromSession(session);
// 返回頭像路徑/upload/test.png,相對路徑String avatar = "/upload"+filename;userService.changeAvatar(uid,avatar,username);
// 返回用戶頭像的路徑給前端頁面,將來用于頭像的展示使用return new JsonResult<>(OK,avatar);}
這里沒有辦法測試,數據類型模擬不出來,file每一種類型的文件編碼都不一樣。?
🚀4.上傳頭像 -前端頁面
在upload頁面中編寫上傳頭像的代碼。.ajax()請求它可以把一個數據解析成一個大串,來拼接在參數名的后邊,但file可以直接把一個文件作為一個整體提交。表單中action可以整體提交。
說明:如果直接使用表單進行文件的上傳,需要給表單顯示的添加一個屬性:
enctype="multipart/form-data";聲明出來,不會將目標文件的數據結構做修改再上傳,不同字符串。
🚀5.解決BUG
?5.1更改默認的大小限制
SpringMVC默認為1MB文件可以進行上傳,手動的去修改SpringMVC默認上傳文件愛的大小。
方式一:直接可以在配置文件中進行配置:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB
?5.2顯示頭像
頁面中通過ajax的請求來提交文件,提交完成后返回了json串,解析出data中的數據,設置到img頭像標簽的src屬性上就可以了。
1.在這里把submit改成button,因為submit不能夠添加點擊事件。
<input id="btn-change-avatar" type="button" class="btn btn-primary" value="上傳" />
2.這里原來的serialize提交的是一個串的類型,不能夠解析文件,所以要換成類似一下方式:
- serialize():可以將表單數據自動拼接成key=value的結構進行提交給服務器,一般提交是普通控件類型中的數據(text/password/radio/checkbox)。
- FromData類:將表單中的數據保持原有的結構進行提交。new FromData($("#form")[0]);將form表單中某一個元素的整體值作為FromData()創建對象的數據,所以這個對象的格式就放在了這個對象當中,這樣格式就不會被看做其他的對象被更改或調整。
- ?ajax默認處理數據時按照字符串的形式進行處理,以及默認會采用字符串的形式進行提交數據。關閉這兩個默認的功能。
new FromData($("#form")[0])
?5.3登錄后顯示頭像
可以更新頭像成功后,將服務器返回的頭像路徑保存在客戶端的cookie對象中,然后每次檢測到用戶打開上傳頭像頁面,在這個頁面中通過ready()方法來自動監測去讀取cookie中的頭像并設到src上。
1.設置cookie中的值:
導入cookie.js的文件:
<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script><script src="../js/autoLogin.js" type="text/javascript"></script>
調用cookie的方法:
$.cookie(key,value,time); //單位:天(存活時間)
2.在upload.html頁面先引入cookie.js文件
<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script><script src="../js/autoLogin.js" type="text/javascript"></script>
3.在upload.html頁面通過ready()自動讀取cookie中的數據。
$(document).ready(function (){let avatar = $.cookie("avatar");// 將cookie中的值獲取出來設置到頭像src屬性上$("#img-avatar").attr("src",avatar);});
5.4顯示最新頭像
在更改完頭像后,將最新的頭像地址,再次保存到cookie,同名保存會覆蓋原有cookie中的值。
$.cookie("avatar",json.data,{expires: 7});
🎃1.新增收貨地址-數據表創建
CREATE TABLE t_address (aid INT AUTO_INCREMENT COMMENT '收貨地址id',uid INT COMMENT '歸屬的用戶id',`name` VARCHAR(20) COMMENT '收貨人姓名',province_name VARCHAR(15) COMMENT '省-名稱',province_code CHAR(6) COMMENT '省-行政代號',city_name VARCHAR(15) COMMENT '市-名稱',city_code CHAR(6) COMMENT '市-行政代號',area_name VARCHAR(15) COMMENT '區-名稱',area_code CHAR(6) COMMENT '區-行政代號',zip CHAR(6) COMMENT '郵政編碼',address VARCHAR(50) COMMENT '詳細地址',phone VARCHAR(20) COMMENT '手機',tel VARCHAR(20) COMMENT '固話',tag VARCHAR(6) COMMENT '標簽',is_default INT COMMENT '是否默認:0-不默認,1-默認',created_user VARCHAR(20) COMMENT '創建人',created_time DATETIME COMMENT '創建時間',modified_user VARCHAR(20) COMMENT '修改人',modified_time DATETIME COMMENT '修改時間',PRIMARY KEY (aid)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
?🎃2.新增收貨地址-創建實體類
創建一個類Address類,在類中定義表的相關字段,采用駝峰命名方式,最后再去繼承BaseEntity類。
//收貨地址數據的實體類
public class Address extends BaseEntity {private Integer aid;private Integer uid;private String name;private String provinceName;private String provinceCode;private String cityName;private String cityCode;private String areaName;private String areaCode;private String zip;private String address;private String phone;private String tel;private String tag;private Integer isDefault;
}
🎃3.新增收貨地址-持久層
?1.1各功能的開發順序
當前收貨地址功能模塊:列表的展示、修改、刪除、設置默認、新增收貨地址。開發順序:新增收貨地址->列表展示->設置默認收貨地址->刪除收貨地址->修改收貨地址。
?1.2規劃需要執行的SQL語句
1.對應的插入語句:
insert into t_address(除aid外的字段列表) values(字段值列表)
2. 一個用戶的收貨地址最多只能有20條數據對應。在插入用戶數據之前先做查詢操作。收貨地址邏輯控制方面的一個異常。
select count(*) from t_address where uid=?
?1.3接口與抽象方法
1.創建一個新的接口Address,在這個接口中來定義上面兩個SQL語句抽象方法定義。
/*** 插入用戶的收貨地址* @param address 收貨地址數據* @return 受影響的行數*/Integer insert(Address address);/*** 根據用戶的id統計收貨地址的數量* @param uid 用戶的id* @return 當前用戶的收貨地址總數*/Integer count(Integer uid);
?1.4配置SQL映射
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cy.store.mapper.AddressMapper"><resultMap id="AddressEntityMap" type="com.cy.store.entity.Address"><id column="aid" property="aid"/><result column="province_code" property="provinceCode"/><result column="province_name" property="provinceName"/><result column="city_code" property="cityCode"/><result column="city_name" property="cityName"/><result column="area_code" property="areaCode"/><result column="area_name" property="areaName"/><result column="is_delete" property="isDelete"></result><result column="created_user" property="createdUser"></result><result column="created_time" property="createdTime"></result><result column="modified_user" property="modifiedUser"></result><result column="modified_time" property="modifiedTime"></result></resultMap><insert id="insert" useGeneratedKeys="true" keyProperty="aid">insert into t_address (uid, name, province_name, province_code, city_name, city_code, area_name, area_code, zip,address, phone, tel,tag, is_default, created_user, created_time, modified_user, modified_time) values (#{uid}, #{name}, #{provinceName}, #{provinceCode}, #{cityName}, #{cityCode}, #{areaName},#{areaCode}, #{zip}, #{address}, #{phone}, #{tel}, #{tag}, #{isDefault}, #{createdUser},#{createdTime}, #{modifiedUser}, #{modifiedTime})</insert><select id="countByUid" resultType="java.lang.Integer">select count(*) from t_address where uid=#{uid}</select>
</mapper>
2.在test下的mapper文件夾下創建一個AddressMapperTests的測試類。
@Testpublic void insert(){Address address = new Address();address.setUid(6);address.setPhone("15374583927");address.setName("備備");addressMapper.insert(address);}@Testpublic void countByUid(){Integer count = addressMapper.countByUid(6);System.out.println(count);}
🎃4.新增收貨地址-業務層
?4.1規劃異常
1.如果用戶是第一次插入收貨地址,規則:當用戶插入的地址是第一條時,需要將當前地址作為默認收貨地址,如果查詢到統計總數為0,則將當前地址的is_default設置為1。查詢統計的結果為0不代表異常。
查詢到的結果大于20,這時候需要拋出業務控制的異常AddressCountLimitException異常。自行創建這個異常。
public class AddressCountLimitException extends ServiceException{public AddressCountLimitException() {super();}public AddressCountLimitException(String message) {super(message);}public AddressCountLimitException(String message, Throwable cause) {super(message, cause);}public AddressCountLimitException(Throwable cause) {super(cause);}protected AddressCountLimitException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
2.插入數據時產生異常。?
?4.2接口與抽象方法
1.創建一個IAddressService接口,在里面定義業務的抽象方法。
void addNewAddress(Integer uid,String username,Address address);
2.創建一個AddressServiceImpl實現類,去實現接口中的抽象方法。
在配置文件中定義數據
#Spring讀取配置文件中數據:@Value(${user.address.max-count})
user.address.max-count=20
?4.3實現抽象方法?
在實現類中實現業務控制 。
//新增收貨地址的實現類
@Service
public class AddressServiceImpl implements IAddressService {@Autowiredprivate AddressMapper addressMapper;@Value("${user.address.max-count}")private Integer count;@Overridepublic void addNewAddress(Integer uid, String username, Address address) {
// 調用收貨地址統計的方法Integer count = addressMapper.countByUid(uid);if(count >= 20){throw new AddressCountLimitException("用戶收貨地址超出上限");}// uid、isDeleteaddress.setUid(uid);Integer isDelete = count == 0?1:0; //1表示默認,0表示不默認address.setIsDefault(isDelete);
// 補全4項日志address.setCreatedUser(username);address.setCreatedTime(new Date());address.setModifiedUser(username);address.setModifiedTime(new Date());// 插入收貨地址的方法Integer rows = addressMapper.insert(address);if(rows != 1){throw new InsertException("插入用戶地址時產生未知異常");}}}
3.測試業務層功能是否正常。AddressServiceTests測試來測試業務功能。
@Testpublic void addNewAddress(){Address address = new Address();address.setPhone("11874583927");address.setName("備備");addressService.addNewAddress(6,"小明",address);}
🎃5.新增收貨地址-控制層
?5.1處理異常
業務層拋出了收貨地址總數超標的異常,在BaseController中進行處理。
else if(e instanceof AddressCountLimitException) {result.setState(4003);result.setMessage("用戶收貨地址超出上限的異常");}
?5.2設計請求
請求路徑:/addresses/add_new_address
請求方式:POST
請求數據:Address address,HttpSession session
響應結果:JsonResult<Void>
?5.3處理請求
在控制層創建AddressController來處理用戶收貨地址的請求和響應。
@RequestMapping("add_new_address")public JsonResult<Void> addNewAddress(Address address, HttpSession session){Integer uid = getuidFromSession(session);String username = getUsernameFromSession(session);addressService.addNewAddress(uid,username,address);return new JsonResult<>(OK);}
先登錄用戶,然后再訪問http://localhost:8080/address/add_new_address?name=tom&phone=1826478328?進行測試。
🎃6.新增收貨地址-前端頁面
<script>$("#btn-add-new-address").click(function (){$.ajax({url:"/addresses/add_new_address",type:"POST",data:$("#form-add-new-address").serialize(),dataType:"JSON",success:function (json){if(json.state==200){alert("新增收貨地址成功");}else{alert("新增收貨地址失敗");}},error:function (xhr){alert("新增收貨地址時產生未知的異常"+xhr.message);}});});</script>