SpringBoot電商平台實戰(1) 前言
在練習的差不多之後,決定實作一個完整的電商平台「SpringBoot電商平台實戰」,也參考了不少網路上的資源,主要是參考CSDN上的雨醉東風(由於不知能不能引用所以有興趣可以自行查詢)來撰寫api,參考架構,差別最大的是該教學是使用MyBatis做資料庫操作,而我是使用JDBC,預計將來會再嘗試Redis、SpringSecurity、再加上前端完成一個完整的網站。
本來在2021年的10月初已經做好了,但遇到需要實作記住持續登入的功能,雖然SpringSecurity做得到,但部分功能都涉及到session,還有登入時候的POST方法驗證登入,變成了SpringSecurity + 自己做的POST一起登入的問題,還有持續保持登入狀態等問題,將來會再更熟悉SpringSecurity再把他做整合。
本例相關工具、環境、語言、配置
開發環境:
- Java 11
- IntelliJ IDEA 2020.3.2
- Spring Boot 2.3.7 RELEASE
工具:
Lombok(使用教學)用途:提高開發效率,自動產生getter以及setter等等。
資料庫:MySql 8.0.22
操作資料庫方法:JDBC
前端模板引擎:Freemarker
其他:
前端:html、css、javascript、jquery、ajax
學習到的新知識:
密碼加密、統一異常處例、AOP、MVC、Spring Session
如果以上有東西不熟悉的話,可以參考本網站個文章來做參考。
application.properties配置
#DB Configuration
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db_store?serverTimezone=Asia/Taipei&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=springboot
#Spring Session
## Session 存儲方式
spring.session.store-type=redis
## Session 過期時間,默認單位為 s
server.servlet.session.timeout=600
## Session 存儲到 Redis 鍵的前綴
spring.session.redis.namespace=test:spring:session
#Freemaker設定
spring.freemarker.cache=false
# 模版後綴名 默認為ftl
spring.freemarker.suffix=.ftl
# 文件編碼
spring.freemarker.charset=UTF-8
# 模版加載的目錄
spring.freemarker.template-loader-path=classpath:/templates/
# 靜態資源訪問路徑
spring.mvc.static-path-pattern=/static/**
# 獲取根目錄路徑
spring.freemarker.request-context-attribute=request
maven設定
本例子先使用了Lombok、jdbc、mysql、跨域註解的commons-codec、Freemarker
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
規劃資料庫中的用戶數據
首先,先創一個名為db_store的資料庫
CREATE DATABASE db_store
USE db_store
再來資料表創建相關數據,這邊學到了一個東西,就是鹽值,是將原密碼透過某種加密方式而產生的新密碼名稱,詳細介紹下面會解釋。
CREATE TABLE t_user (
uid INT AUTO_INCREMENT PRIMARY KEY ,
username VARCHAR(20) UNIQUE NOT NULL,
password CHAR(32) NOT NULL,
salt CHAR(60) COMMENT'鹽值',
gender INT(1) COMMENT '性别:0-女,1-男',
phone VARCHAR(20),
email VARCHAR(50),
avatar VARCHAR(100) COMMENT'頭像',
is_delete INT(1) COMMENT '是否删除:0-否,1-是',
created_user VARCHAR(20) COMMENT'創建人',
created_time DATETIME COMMENT '創建時間',
modified_user VARCHAR(20)COMMENT'最後修改人',
modified_time DATETIME COMMENT'最後修改時間',
)
創建用戶的實體類別
基類(Entity)
在這邊,要先創建所有類別的基類,意思就是在將來所有實體的類別,例如商品、用戶等等都會用到這個基類,那這個類別我們把它命名為BaseEntity,詳細功能在本例中就是會記錄上最後修改的時間、使用者等等,大家應該看例子就知道了
在這邊也學到了序列化,簡單說就是在電腦上,我們可以把資料順利的儲存在Ram上面,以便在執行上更順利,詳細的解說我也不太清楚,總之就是(以下網路參考)
1、當想把的內存中的對象狀態保存到一個文件中或者資料庫中時候;
2、當想用套接字在網絡上傳送對象的時候;
3、當想通過RMI傳輸對象的時候;
用上Serializable就對了!
/*全部實體類別的基底類別*/
//implements Serializable序列化
@Data
public class BaseEntity implements Serializable {
private String created_user ;
private Date created_time;
private String modified_user;
private Date modified_time;
}
User Class
接下來創建User 這個類別,當然繼承了BaseEntity
@Data
public class User extends BaseEntity implements Serializable {
private Integer uid;
private String username;
private String password;
private String updatepassword;
private Integer gender;
private String phone;
private String email;
private String avatar;
private Integer isDelete;
private String role;
private Integer enabled;
}
UserRowMapper
public class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user=new User();
//後面result.getxx(),()裡面放的是要取得的資料在資料庫中的名字
user.setUid(resultSet.getInt("uid"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
user.setSalt(resultSet.getString("Salt"));
user.setGender(resultSet.getInt("gender"));
user.setPhone(resultSet.getString("phone"));
user.setEmail(resultSet.getString("email"));
user.setAvatar(resultSet.getString("avatar"));
user.setIsDelete(resultSet.getInt("is_delete"));
user.setCreated_user(resultSet.getString("created_user"));
user.setCreated_time(resultSet.getTime("created_time"));
user.setModified_user(resultSet.getString("modified_user"));
user.setModified_time(resultSet.getTime("modified_time"));
return user;
}
}
創建用戶、用戶登入、修改數據、查詢數據
User-Dao
Interface
public interface UserDao {
//創建用戶
String CreateUser(User user);
//根據用戶名稱username查詢數據
List<User> ReadByUsername(String username);
//根據用戶uid查詢數據
List<User> ReadByUid(Integer uid);
//根據用戶uid修改數據
String UpdateByUid(Integer uid,User user);
}
Implement
@Component
public class UserImpl implements UserDao{
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Override
public String CreateUser(User user) {
String sql ="INSERT INTO t_user" +
"(username,password,salt,gender,phone,email,avatar,is_delete,created_user,created_time,modified_user,modified_time)" +
"VALUE" +
"(:userName,:userPassword,:Salt,:Gender,:Phone,:Email,:Avatar,:Is_delete,:Create_user,:Create_time,:Modified_user,:Modified_time)";
Map<String, Object> map =new HashMap<>();
map.put("userName",user.getUsername());
map.put("userPassword",user.getPassword());
map.put("Salt",user.getSalt());
map.put("Gender",user.getGender());
map.put("Phone",user.getPhone());
map.put("Email",user.getEmail());
map.put("Avatar",user.getAvatar());
map.put("Is_delete",user.getIsDelete());
map.put("Create_user",user.getCreated_user());
map.put("Create_time",user.getCreated_time());
map.put("Modified_user",user.getModified_user());
map.put("Modified_time",user.getModified_time());
namedParameterJdbcTemplate.update(sql,map);
return ("註冊成功");
}
@Override
public List<User> ReadByUsername(String username) {
String sql="SELECT * FROM t_user WHERE username=:userName";
Map<String,Object>map= new HashMap<>();
map.put("userName",username);
List<User> list =namedParameterJdbcTemplate.query(sql,map,new UserRowMapper());
if(list.size()>0){
return list;
}else {
//如果是空集合[]
return null;
}
}
@Override
public List<User> ReadByUid(Integer uid) {
String sql="SELECT * FROM t_user WHERE uid=:Uid";
Map<String,Object>map= new HashMap<>();
map.put("Uid",uid);
List<User> list =namedParameterJdbcTemplate.query(sql,map,new UserRowMapper());
if(list.size()>0){
return list;
}else {
//如果是空集合[]
return null;
}
}
@Override
public String UpdateByUid(Integer uid, User user) {
String sql ="UPDATE t_user SET " +
"phone=:Phone,email=:Email,gender=:Gender ,modified_user=:Modified_user,modified_time=:Modified_time" +
" WHERE uid=:Uid";
Map<String, Object> map =new HashMap<>();
map.put("Gender",user.getGender());
map.put("Phone",user.getPhone());
map.put("Email",user.getEmail());
map.put("Modified_user",user.getModified_user());
map.put("Modified_time",user.getModified_time());
map.put("Uid",uid);
namedParameterJdbcTemplate.update(sql,map);
return "修改成功";
}
}
User-Service
這邊學到了一個程式上設計的知識,就是在做創建用戶的service層時,也會順便做ReadByUsername(String username)這個依據用戶名稱查詢資料的方法,目的就是實作我們實際上要避免用戶名稱重複,或是做修改資料的時候找不到帳戶等等的概念。
Interface
public interface UserService {
//創建用戶
String CreateUser(User user);
//根據用戶名稱username查詢數據
List<User> ReadByUsername(String username);
//登入
List<User> login(String username,String password);
//修改
String UpdateByUid(Integer uid,User user);
}
Implements
這邊學到了一個密碼加密的方法,就是先在後端產生一串隨機的亂碼,就是鹽值(Salt),之後再經過自定義的方法,把使用者的密碼做計算之後在產生一組新的密碼,
簡單來說:
即使別人擁有了鹽值過後,他也沒辦法得知我們在後端驗證演算的方式,我們可以是加上一組固定的數字,加減乘除等等都可以,鹽值經過這些計算之後再到資料庫已儲存的加密結果做驗證。
所以我們來實作這些功能部分需要的工具
鹽值的產生
//https://www.jianshu.com/p/2c62ae005db4 參考網址
public class UUIDutil {
public static String uuid(){
return UUID.randomUUID().toString().replace("-","");
}
}
MD5加密
這邊直接把鹽值再加上使用者輸入的原始密碼加上之後,在做MD5加密產生在資料庫儲存的密碼
@Component
public class MD5util {
public static String md5(String src ,String salt){
//這邊使用了springframework的加密方式
//md5DigestAsHex參數是Bytes,所以透過java String類將字串轉為Bytes
String result=src+ salt;
return DigestUtils.md5DigestAsHex(result.getBytes());
}
}
完整程式碼
@Component
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Autowired
private RedisTemplate redisTemplate;
@Override
public String CreateUser(User user) {
//先檢查是否有重複名稱
String username =user.getUsername();
//如果查詢結果不為null,代表有重複名稱
if(userDao.ReadByUsername(username) != null){
// 拋出錯誤
throw new IllegalArgumentException("用戶名稱重複");
}
//如果可以執行到這一行,代表用戶名稱沒有被使用
//存取當下時間
Date now =new Date();
//密碼加密
String salt = UUIDutil.uuid();
user.setSalt(salt);
String md5Password = MD5util.md5(user.getPassword(),salt);
user.setPassword(md5Password);
//開始設置其他後台參數,時間,使用者等等
user.setIsDelete(0);
user.setCreated_user(username);
user.setCreated_time(now);
user.setModified_user(username);
user.setModified_time(now);
return userDao.CreateUser(user);
}
@Override
public List<User> login(String username, String password) {
//新增陣列,來接受userDao.ReadByUsername的回傳資料
List<User> list=new ArrayList<>();
list=userDao.ReadByUsername(username);
//因為只會有一筆,所以在陣列的第0個位置
User user= list.get(0);
if(userDao.ReadByUsername(username)== null){
// 拋出錯誤
throw new IllegalArgumentException("登入失敗,找不到帳號");
}
if(user.getIsDelete()==1){
throw new IllegalArgumentException("登入失敗,帳號已經被刪除");
}
String salt = user.getSalt();
String md5Password = MD5util.md5(password,salt);
if(!user.getPassword().equals(md5Password)){
throw new PasswordExpiredException("密碼輸入錯誤,請重新輸入");
}
return list;
}
@Override
public List<User> ReadByUsername(String username) {
return userDao.ReadByUsername(username);
}
@Override
public String UpdateByUid(Integer uid, User user) {
if(userDao.ReadByUid(uid)== null){
// 拋出錯誤
throw new IllegalArgumentException("獲取數據失敗,請再重新嘗試一次");
}
//新增陣列,來接受userDao.ReadByUsername的回傳資料
List<User> list=new ArrayList<>();
list=userDao.ReadByUid(uid);
//因為只會有一筆,所以在陣列的第0個位置
User olduser= list.get(0);
if(olduser.getIsDelete()==1){
throw new IllegalArgumentException("登入失敗,帳號已經被刪除");
}
olduser.setEmail(user.getEmail());
olduser.setPhone(user.getPhone());
olduser.setGender(user.getGender());
olduser.setModified_time(new Date());
olduser.setModified_user(user.getModified_user());
return userDao.UpdateByUid(uid,olduser);
}
}
如何維持登入資訊
在這邊我卡了很久,因為記住登入的方法雖然Spring Security中有這個功能,但是在獲取資料上還沒有學習到,因為USER Controller也需要獲取這些記住的資訊,但也因為在整合上還沒有觸碰到(因為已經做了差不多了),所以打算以後先單獨學習再來做整合,才不會遇到連Service層都需要重做的問題,所以就去了解了Cookie、Session、Token(這邊卡超久去理解QAQ)。
Cookie、Session、Token是什麼?
參考網址 在看完文章以及一些實作的影片之後,也特別推薦這部Youtube的影片,這次決定用Session,也把目前得知的知識記錄下來,
Cookie:儲存在瀏覽器裡面,裡面存著cookie的名字、cookie值(也因為這樣Cookie 只支持存字符串數據)、在哪一個domain、安全性等等…(上面影片有詳細說明),藉由request header傳遞,藉由設定過期時間來使瀏覽器記住Cookie,這樣關閉瀏覽器在開啟之後就還是會存在在瀏覽器中。
Session:session 是另一種記錄服務器和客戶端會話狀態的機制,其實Session是跟Cookie一樣的東西,只是它儲存在伺服器當中,透過(Key,Value)儲存在伺服器當中,使用者透過儲存在Cookie中的SessionId傳送到伺服器獲取資訊。
SessionId:他其實就是儲存在Cookie中的Cookie Name叫SessionId的Value,裡面記錄一組SessionId。
SessionId的產生(參考),是由伺服器所產生的,再一次對話中都會產生,在Java裡面就是使用
HttpServletRequest.getSession(ture)
這邊相等於HttpServletRequest.getSession()
,因為預設是true,這邊的意思就是如果沒有得到SessionID的時候伺服器就會自動產生了。
所以簡單說,使用者的瀏覽器Cookie儲存著一組SessionID(號碼牌),之後使用者再藉由Cookie傳送到伺服器裡面,伺服器就會藉由SessionID,來在伺服器中藉由ke來獲取 value
Token:更複雜,但是更安全,Spring Security就是用這種方式來實現remember me的機制,日後再來更新。
透過實做攔截器來維持登入
我們就透過攔截器,來攔截HttpServletRequest中的Session,裡面存放用戶的uid,如果為空,返回302狀態碼,另外也學習到了如何使用HttpServletRequest request以及HttpServletResponse response,本來在想說是不是要新增類別把之後,用變數接住在透過return回傳回去,結果測試了一下根本不用,直接在方法裡面操作request以及response就好。
新增類別:
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (request.getSession().getAttribute("uid") == null) {
System.out.println("session中的uid為null");
response.setStatus(302);
return false;
}
//System.out.println("session中的uid為= " + request.getSession().getAttribute("uid"));
return true;
}
}
透過@Configuration來設定
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
/*
//在這個patterns裡面就是不用登入的
List<String> patterns = new ArrayList<>();
patterns.add("/user_login");
patterns.add("/static/**");
*/
//有些之後會用到
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/login_success")
.addPathPatterns("/carts/**")
.addPathPatterns("/users/**");
//.excludePathPatterns(patterns);
}
}
這樣之後只要call 跟/user/**有關的API,就會先被攔截器攔截起來,經過驗證有無獲取user Id之後,才能進行相關操作,甚至在Controller裡面也可以再接住session一次,就可以在Service中跟User.getId()來進一步驗證id是否相同。
User-Controller
這邊會加上@CrossOrigin(origins = “*")的原因目前還不清楚,只是這個是跨網域專用的,因為沒有加上去時候雖然APITester沒問題,但透過Ajax call API的時候,ajax 會得不到物件的資料,得到的數據是”{‘readyState’:0,‘status’:0,‘statusText’:‘error’}",查了很久才發現加上這個就好了。
@RestController
@CrossOrigin(origins = "*")
public class UserController {
@Autowired
private UserService userService;
//用戶註冊
@PostMapping("/register")
public String create(@RequestBody User user){
return userService.CreateUser(user);
}
//用戶登入
@PostMapping("/userlogin")
public List<User> userlogin(HttpSession session, @RequestBody User user){
List<User> list =new ArrayList<>();
list =userService.login(user.getUsername(),user.getPassword());
//登入成功後設置session的值
session.setAttribute("uid",list.get(0).getUid());
session.setAttribute("username",list.get(0).getUsername());
return (list);
}
@PutMapping("users/{uid}") //根據帳號做修改
public String update(@PathVariable Integer uid,@RequestBody User user){ //@Path用來取得url路徑的值
return userService.UpdateByUid(uid,user);
}
@GetMapping("users/{username}")
public List<User> read(@PathVariable String username){ //@Path用來取得url路徑的值
return userService.ReadByUsername(username);
}
//用來獲取session中的username,在前端某些需要的地方會用到。
@GetMapping("users/session-username")
public String getsessionusername(HttpSession session){ //@Path用來取得url路徑的值
return (String)session.getAttribute("username");
}
//用戶登出
@GetMapping("/sign_out")
public String signout(HttpSession session){
//銷毀session中的KV
session.removeAttribute("uid");
session.removeAttribute("username");
return "登出成功";
}
}
規劃前端Javascript
登入頁面的Javascript
var apiurl={
loginurl:"/userlogin",
successloginurl:"/"
}
$("#enter").click(function () {
var username = $("#username").val();
var password = $("#password").val();
$.post({
url:apiurl.loginurl,
contentType:"application/json;charset=UTF-8",
data:JSON.stringify({"username":username,"password":password}),
success:function(res) {
//console.log(res);
window.location.href = apiurl.successloginurl;//正確登入後頁面跳轉回首頁
}
});
});
註冊頁面Javascript
var apiurl={
registerurl:"/register",
registersuccess:"/"
}
$("#sub").click(function () {
var username = $("#username").val();
var password = $("#password").val();
var phone = $("#phone").val();
var email = $("#email").val();
var gender = 0;
gender=$("input[name='gender']:checked").val();
$.post({
url:apiurl.registerurl,
contentType:"application/json;charset=UTF-8",
data:JSON.stringify({"username":username,"password":password,"gender":gender,"phone":phone,"email":email}),
success:function(res) {
alert("註冊成功");
window.location.href = apiurl.registersuccess;//正確登入後頁面跳轉
}
});
});
最重要的Javascript碼,前端得知使否登入
主要透過Checklogin();檢查是否登入來決定html的狀況,例如是登入按鈕還是登出按鈕,
在別的頁面,例如加入購物車時、前往個人商品頁時,都可以先透過/users/session-username這個API,如果進去的話就會反回username,沒有登入的話就會被攔截器返回302 error。
//載入頁面時先在入熱門商品資訊(之後文章會寫)
//再來進入Checklogin();檢查是否登入來決定html的狀況,例如是登入按鈕還是登出按鈕
$(document).ready(function(){
showHotList();
Checklogin();
});
function showHotList() {
$("#hot-list").empty();
$.ajax({
url: "/products/list/hot",
dataType: "json",
success:function(data) {
hotlist=data;
console.log(hotlist);
for (var i = 0; i < hotlist.length; i++) {
var html ='<div class="content col-md-2">'
+'<div class="title">#{title}</div>'
+'<div class="price">$#{price}</div><a class="d-flex justify-content-end" href="#{href}">查看商品</a>'
+'</div>';
html = html.replace(/#{title}/g, hotlist[i].title);
html = html.replace(/#{price}/g, hotlist[i].price);
html = html.replace(/#{href}/g, "/product?id=" + hotlist[i].id);
$("#hot-list").append(html);
}
}
});
}
var username=" ";
function Checklogin() {
$.ajax({
url: "/users/session-username",
dataType: "text",
success:function(data) {
console.log(data);
var html;
html='<label>#{username}</label><a href="/sign_out">登出</a>';
html = html.replace(/#{username}/g, data);
$("#check").append(html);
username=data;
},error:function (){
//alert("沒登入");
}
});
}
//在頁面Checklogin()時,會依據有無登入來設定js變數username的值,如果沒有設置到的話代表沒有登入,
//所以按下修改資料的超連結按鈕時,就會進行阻擋反饋
$(document).on("click",'#modified',function(){
if(username == " "){
alert("尚未登入");
}else{
window.location.href ="/users/@"+ username;
}
});
@ControllerAdvice統一處理異常
springboot提供了可以用來攔截並處理應用程式中全部Controller所拋出的Exception例外錯誤的註解@ControllerAdvice,由於用法很固定,所以也不多說明,但目前沒有很摸懂各種錯誤詳細的錯誤碼是多少,目前也只把種類(2-5)開頭用正確而已
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handle(IllegalArgumentException exception) {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("IllegalArgumentException:" + exception.getMessage());
}
}
規劃本專案所有返回Model的Controller
@org.springframework.stereotype.Controller
public class Controller {
@GetMapping("/login")
public String loginpage(Model model)
{
return "login";
}
//登入頁面
@GetMapping("/login_success")
public String successpage(Model model)
{
return "/login_success";
}
//首頁
@GetMapping("/")
public String index(Model model)
{
return "/index";
}
//註冊頁面
@GetMapping("/register")
public String register(Model model){ return "/register";}
//user修改資料頁面
@GetMapping("/users/@{username}")
public String usermodified(Model model){ return "/user_modified";}
//商品詳情頁面
@GetMapping("/product")
public String products(Model model){ return "/product";}
//購物車詳情頁面
@GetMapping("/mycarts")
public String mycarts(Model model){ return "/mycarts";}
}
其他頁面HTML、CSS、JS以及狀態程式碼、class資料夾位置都放在github裡面。需要的大家可以去看看。