睿乐购电商课程设计——总结
项目文件目录说明命名说明与注意事项ajax参考问题记录jquery 获取表单数据前后端分离方案session与cookietoken参考代码
接口注意事项mybatis注解使用mysql问题支付宝沙盒模拟集成Swagger2文档集成admin参考文档
项目文件目录说明
html文件都在 src/main/resources/static目录下后台管理系统的文件位于src/main/resources/static/admin下前端编写放置在 src/main/resources/static/admin/rlg下接口编写再 src/main/java编写
annotation:自定义注解config: 配置文件controller: 控制层,接口处理exception: 自定义异常类mapper: 模型层,往数据库查询数据popj: 数据库表对应的实体类service: 业务层接口
impl: 接口实现类 util: 工具类handler: 处理器ShopApplication: 启动器 es.sql : 项目的数据库
管理员账户密码:admin ysuadmin
命名说明与注意事项
采用REST风格
get: 获取数据post: 添加数据delete: 删除数据put: 修改数据
操作命名
getUserById: 表示通过id获取用户getUserList: 表示获取用户列表addUser : 表示添加用户delUser : 表示删除用户updUser : 表示修改用户By: 表示通过某些字段进行某些操作Num|Count: 表示获取数据的大小
controller说明
对于add操作,表里有的默认字段前端可以不传,mapper必须写,添加前需要校验,为空添加默认字段
在对于测试的时候:使用日志输出
import org
.slf4j
.Logger
;
import org
.slf4j
.LoggerFactory
;
private Logger log
= LoggerFactory
.getLogger(UserController
.class);
log
.debug("输出字段信息")
ajax参考
function login() {
var userName
= $("#username").val();
var password
= $("#password").val();
if (isNull(userName
)) {
alert("请输入用户名!");
return;
}
if (!validUserName(userName
)) {
alert("请输入正确的用户名!");
return;
}
if (isNull(password
)) {
alert("请输入密码!");
return;
}
if (!validPassword(password
)) {
alert("请输入正确的密码!");
return;
}
var data
= { "username": userName
, "password": password
}
$
.ajax({
type
: "POST",
dataType
: "json",
url
: "http://localhost:8082/user/login",
contentType
: "application/json; charset=utf-8",
data
: JSON.stringify(data
),
success
: function(result
) {
console
.log(result
);
if (result
.status
== 200) {
setCookie("token", result
.data
.userToken
);
setCookie("username",username
);
window
.location
.href
= "index.html";
};
if (result
.status
== 406 ) {
alert("登陆失败!请检查账号和密码!");
return;
}
},
error
: function() {
alert("接口异常,请联系管理员!");
return;
}
});
}
问题记录
数据库的导入(6.18): source sql文件位置自定义注解解决Springboot发送post请求时string类型参数接收不到的问题(HandlerMethodArgumentResolver )springboot2 前台传送json数据,后台接收数据(6.18)
可以对实体类进行封装,但String不行使用JSONObject 获取json 再获取键值需要在参数前加上@RequestBody注解
jquery 获取表单数据
jquery 获取 获取select标签值
元素内容 $('#searchId').find('option:selected').text()根据name $("select[name='searchId'] option:selected").val();根据id $("#searchId option:selected").val()设置value为pxx的项选中 $("#searchId").val("pxx");设置text为pxx的项选中 $("#searchId").find("option:contains('pxx')").attr("selected",true);
前后端分离方案
session与cookie
Cookie是浏览器(User Agent)访问一些网站后,这些网站存放在客户端的一组数据,用于使网站等跟踪用户,实现用户自定义功能。Cookie的Domain和Path属性标识了这个Cookie是哪一个网站发送给浏览器的;Cookie的Expires属性标识了Cookie的有 效时间,当Cookie的有效时间过了之后,这些数据就被自动删除了。Session是存放在服务器端的类似于HashTable结构(每一种Web开发技术的实现可能不一样,下文直接称之为HashTable)来存放用户 数据,当浏览器第一次发送请求时,服务器自动生成了一个HashTable和一个Session ID用来唯一标识这个HashTable,并将其通过响应发送到浏览器。当浏览器第二次发送请求,会将前一次服务器响应中的Session ID放在请求中一并发送到服务器上,服务器从请求中提取出Session ID,并和保存的所有Session ID进行对比,找到这个用户对应的HashTable。
token
首次登录时,后端服务器判断用户账号密码正确之后,根据用户id、用户名、定义好的秘钥、过期时间 生成 token ,返回给前端
前端拿到后端返回的 token ,存储在 localStroage 里
赋值:localStorage.setItem("token", result.data.token); | localStorage.token= | localStorage[token]=读取信息:localStorage.getItem("token") | localStorage.token | localStorage[token]删除localStorage指定键对应的值:localStorage.removeItem('token');删除localStorage所有值: localStorage.clear();
js使用cookie保存token(cookie在http请求中,随着请求发送到服务器)
保存token: sessionStorage.setItem("token","value");获取token: sessionStorage.getItem("token")删除: sessionStorage.removeItem("key");删除保存的所有数据: sessionStorage.clear()
前端每次路由跳转, 判断 localStroage 有无 token ,没有则跳转到登录页,有则请求获取用户信息,改变登录状态
每次请求接口,在 请求头里携带 token
$
.ajax({
type
: "post",
url
: areaComp
,
dataType
: "json",
contentType
: "application/json",
data
: data
,
async: false,
beforeSend
: function(request
) {
request
.setRequestHeader("token", token
);
},
success
: function (data
){})
后端接口 判断 请求头有无 token,没有或者 token 过期,返回401
前端得到 401 状态码,重定向 到登录页面
与cookie相比较的优势:
支持跨域访问 ,将token置于请求头中,而cookie是不支持跨域访问的;
无状态化, 服务端无需存储token ,只需要验证token信息是否正确即可,而session需要在服务端存储,一般是通过cookie中的sessionID在服务端查找对应的session;
无需绑定到一个特殊的身份验证 方案(传统的用户名密码登陆),只需要生成的token是符合我们预期设定的即可;
更适用于移动端 (Android,iOS,小程序等等),像这种原生平台不支持cookie,比如说微信小程序,每一次请求都是一次会话,当然我们可以每次去手动为他添加cookie,详情请查看博主另一篇博客;
避免CSRF跨站伪造攻击 ,还是因为不依赖cookie;
非常适用于RESTful API ,这样可以轻易与各种后端(java,.net,python…)相结合,去耦合
参考代码
cookie操作相关方法
function setCookie(name
, value
) {
var Days
= 30;
var exp
= new Date();
exp
.setTime(exp
.getTime() + Days
* 24 * 60 * 60 * 1000);
document
.cookie
= name
+ "=" + escape(value
) + ";expires=" + exp
.toGMTString() + ";path=/";
}
function getCookie(name
) {
var arr
, reg
= new RegExp("(^| )" + name
+ "=([^;]*)(;|$)");
if (arr
= document
.cookie
.match(reg
))
return unescape(arr
[2]);
else
return null;
}
function delCookie(name
) {
var exp
= new Date();
exp
.setTime(exp
.getTime() - 1);
if (cval
!= null)
document
.cookie
= name
+ "=" + "xxxx;expires=" + exp
.toUTCString();
}
function checkCookie() {
if (getCookie("token") == null) {
window
.location
.href
= "login.html";
}
}
function checkResultCode(code
) {
if (code
== 402) {
window
.location
.href
= "login.html";
}
}
jquery简写
先引入jquery,再引入:jquery.cookie.js;下载:http://plugins.jquery.com/cookie/
$
.cookie('token', 'token_value', { expires
: 10, path
: '/' });
$
.cookie("token")
$
.cookie("名称",null)
$
.cookie('the_cookie','the_value',{
expires
:7,
path
:'/',
domain
:'jquery.com',
secure
:true
})
login
function login() {
var userName
= $("#username").val();
var password
= $("#password").val();
var data
= { "username": userName
, "password": password
}
$
.ajax({
type
: "POST",
dataType
: "json",
url
: "http://localhost:8082/user/login",
contentType
: "application/json; charset=utf-8",
data
: JSON.stringify(data
),
success
: function(result
) {
console
.log(result
);
if (result
.status
== 200) {
setCookie("token", result
.data
.userToken
);
setCookie("username",username
);
window
.location
.href
= "index.html";
};
if (result
.status
== 406 ) {
alert("登陆失败!请检查账号和密码!");
return;
}
},
error
: function() {
alert("接口异常,请联系管理员!");
return;
}
});
}
对于某些页面必须登录才能访问,使用文档加载事件
<body class="hold-transition sidebar-mini" onLoad="checkCookie();">
ajax请求是携带token,比如删除用户
function userDel() {
$
.ajax({
type
: "DELETE",
url
: "users/delete/1",
contentType
: "application/json",
beforeSend
: function(request
) {
request
.setRequestHeader("token", getCookie("token"));
},
success
: function(r
) {
if (r
.resultCode
== 200) {
console
.log("删除成功")
} else {
console
.log("删除失败")
}
}
});
}
接口注意事项
必须登录 @TokenToUser User oldUser先判断参数getErrorResult
code, msg1, msg getSuccessResult
非String,传数据 (data)传String msg (msg)
传data (Object) 非实体类
JSONObject jsonObject = new JSONObject(); jsonObject.put("question",user.getQuestion());
@RestController
public class AdminManagerController {
@Resource
private UserService userService
;
@GetMapping("/user/list")
public Result
getUsers(@RequestParam(required
= false) Map
<String, Object> params
, @TokenToUser User oldUser
){
if (StringUtils
.isEmpty(params
.get("page")) || StringUtils
.isEmpty(params
.get("limit"))) {
return ResultGenerator
.getErrorResult(Constants
.RESULT_CODE_PARAM_ERROR
, "参数异常!");
}
if (oldUser
==null
){
return ResultGenerator
.getErrorResult(Constants
.RESULT_CODE_NOT_LOGIN
,"用户未登录,请登录");
}
if (!"A".equals(oldUser
.getRole())){
return ResultGenerator
.getErrorResult("没有权限");
}
PageUtil pageUtil
= new PageUtil(params
);
System
.out
.println(params
);
return ResultGenerator
.getSuccessResult(userService
.getUserPage(pageUtil
));
}
@PostMapping("/user/login")
public Result
login(@RequestBody JSONObject jsonParam
){
String username
= jsonParam
.getString("username");
String password
= jsonParam
.getString("password");
}
@PostMapping("/register")
public Result
register(@RequestBody User user
){
}
}
sql语句
update():SET 最后加 update_time=NOW()传参:#{username}命名问题:方法名不能重复
mybatis注解使用
@Select("select * from t_user where id = #{id}")
public User
getUserById(Integer id
);
@Select("select * from t_user where username like CONCAT('%',#{name},'%') and employeeNum like CONCAT('%',#{num},'%')")
public List
<User> searchUsers(@Param("name") String name
,@Param("num") String num
);
@Select("select * from t_job where id = #{jobId}")
@Results({
@Result(property
="javabean变量",column
="数据库字段名",one
=@One(select
="全类名"))
})
public Job
getJobById(Integer jobId
);
@Select("select m.user_name as userName from m_user m,m_job j where m.user_id=m.id and j.job_id=#{jobId}")
public List
<myJob> getJobByMap(Map map
);
@Select("select * from m_user whereand j.job_id like CONCAT('%',#{jobId},'%')")
public List
<Map> getMyJobs(Map map
);
@Select("select t_id as id,t_username username,t_password as password from t_user")
@Results({@Result(property
="username",column
="t_username")
,@Result(property
="password",column
="t_password") })
public List
<User> selectAll();
mysql问题
安装:需要以管理员的身份运行mysqld install 需要再mysql\bin下运行mysqld remove 是卸载时间不对,相差8个小时
set global time_zone
= '+8:00';
flush
privileges;
select now();
java访问数据库设置时区
jdbc:mysql://localhost:3306/db_es?useSSL=false&serverTimezone=Asia/Shanghai
修改密码 alter user 'root'@'localhost' identified with mysql_native_password by '123456';
支付宝沙盒模拟
导包
<dependency>
<groupId>com.alipay.sdk
</groupId>
<artifactId>alipay-sdk-java
</artifactId>
<version>3.0.0
</version>
</dependency>
配置
# 支付宝网关名、partnerId和appId
open_api_domain=https://openapi.alipaydev.com/gateway.do
mcloud_api_domain=http://mcloudmonitor.com/gateway.do
pid=
app_id=
# RSA私钥、公钥和支付宝公钥
merchant_private_key=
public_key=
#SHA1withRsa对应支付宝公钥
#alipay_public_key =
#SHA256withRsa对应支付宝公钥
alipay_public_key=
# 签名类型: RSA->SHA1withRsa,RSA2->SHA256withRsa
sign_type=RSA2
#页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
return_url=http://192.168.1.20:8082/user/pay/return
#服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
notify_url=http://192.168.1.20:8082/user/pay/notify
charset=utf-8
format=json
logPath=E:\\
# 当面付最大查询次数和查询间隔(毫秒)
max_query_retry=5
query_duration=5000
# 当面付最大撤销次数和撤销间隔(毫秒)
max_cancel_retry=3
cancel_duration=2000
# 交易保障线程第一次调度延迟和调度间隔(秒)
heartbeat_delay=5
heartbeat_duration=900
package com
.ysu
.shop
.config
;
import java
.io
.FileWriter
;
import java
.io
.IOException
;
import org
.springframework
.boot
.context
.properties
.ConfigurationProperties
;
import org
.springframework
.context
.annotation
.PropertySource
;
import org
.springframework
.stereotype
.Component
;
@Component
@ConfigurationProperties(prefix
= "")
@PropertySource(value
= "classpath:zfbinfo.properties", encoding
= "utf-8")
public class AlipayConfig {
private String openApiDomain
;
private String appId
;
private String merchantPrivateKey
;
private String alipayPublicKey
;
private String notifyUrl
;
private String returnUrl
;
private String signType
;
private String charset
;
private String logPath
;
private String format
;
public void logResult(String sWord
) {
FileWriter writer
= null
;
try {
writer
= new FileWriter(logPath
+ "alipay_log_" + System
.currentTimeMillis() + ".txt");
writer
.write(sWord
);
} catch (Exception e
) {
e
.printStackTrace();
} finally {
if (writer
!= null
) {
try {
writer
.close();
} catch (IOException e
) {
e
.printStackTrace();
}
}
}
}
public String
getOpenApiDomain() {
return openApiDomain
;
}
public void setOpenApiDomain(String openApiDomain
) {
this.openApiDomain
= openApiDomain
;
}
public String
getAppId() {
return appId
;
}
public void setAppId(String appId
) {
this.appId
= appId
;
}
public String
getMerchantPrivateKey() {
return merchantPrivateKey
;
}
public void setMerchantPrivateKey(String merchantPrivateKey
) {
this.merchantPrivateKey
= merchantPrivateKey
;
}
public String
getAlipayPublicKey() {
return alipayPublicKey
;
}
public void setAlipayPublicKey(String alipayPublicKey
) {
this.alipayPublicKey
= alipayPublicKey
;
}
public String
getNotifyUrl() {
return notifyUrl
;
}
public void setNotifyUrl(String notifyUrl
) {
this.notifyUrl
= notifyUrl
;
}
public String
getReturnUrl() {
return returnUrl
;
}
public void setReturnUrl(String returnUrl
) {
this.returnUrl
= returnUrl
;
}
public String
getSignType() {
return signType
;
}
public void setSignType(String signType
) {
this.signType
= signType
;
}
public String
getCharset() {
return charset
;
}
public void setCharset(String charset
) {
this.charset
= charset
;
}
public String
getLogPath() {
return logPath
;
}
public void setLogPath(String logPath
) {
this.logPath
= logPath
;
}
public String
getFormat() {
return format
;
}
public void setFormat(String format
) {
this.format
= format
;
}
}
测试
package com
.ysu
.shop
.controller
;
import java
.util
.HashMap
;
import java
.util
.Iterator
;
import java
.util
.Map
;
import javax
.annotation
.Resource
;
import javax
.servlet
.http
.HttpServletRequest
;
import javax
.servlet
.http
.HttpServletResponse
;
import com
.alipay
.api
.AlipayApiException
;
import com
.alipay
.api
.AlipayClient
;
import com
.alipay
.api
.DefaultAlipayClient
;
import com
.alipay
.api
.internal
.util
.AlipaySignature
;
import com
.alipay
.api
.request
.AlipayTradePagePayRequest
;
import com
.ysu
.shop
.annotation
.TokenToUser
;
import com
.ysu
.shop
.config
.AlipayConfig
;
import com
.ysu
.shop
.pojo
.Orders
;
import com
.ysu
.shop
.pojo
.Pay
;
import com
.ysu
.shop
.pojo
.User
;
import com
.ysu
.shop
.service
.OrderService
;
import com
.ysu
.shop
.service
.PayService
;
import com
.ysu
.shop
.util
.CheckUtil
;
import com
.ysu
.shop
.util
.Result
;
import com
.ysu
.shop
.util
.ResultGenerator
;
import org
.slf4j
.Logger
;
import org
.slf4j
.LoggerFactory
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.web
.bind
.annotation
.PathVariable
;
import org
.springframework
.web
.bind
.annotation
.PutMapping
;
import org
.springframework
.web
.bind
.annotation
.RequestMapping
;
import org
.springframework
.web
.bind
.annotation
.RestController
;
import io
.swagger
.annotations
.*
;
import springfox
.documentation
.annotations
.ApiIgnore
;
@RestController
@RequestMapping("/user")
@SuppressWarnings("rawtypes")
@Api(value
="/user/pay",tags
="支付模块")
public class PayController {
private Logger log
= LoggerFactory
.getLogger(PayController
.class);
@Autowired
private AlipayConfig alipayConfig
;
@Resource
private OrderService orderService
;
@Resource
private PayService payService
;
@RequestMapping("/payByAli/{orderNo}")
public void payController(@PathVariable String orderNo
, HttpServletRequest request
, HttpServletResponse response
)
throws Exception
{
Orders order
= orderService
.getOrderByOrderNo(orderNo
);
AlipayClient alipayClient
= new DefaultAlipayClient(alipayConfig
.getOpenApiDomain(), alipayConfig
.getAppId(),
alipayConfig
.getMerchantPrivateKey(), alipayConfig
.getFormat(), alipayConfig
.getCharset(),
alipayConfig
.getAlipayPublicKey(), alipayConfig
.getSignType());
AlipayTradePagePayRequest alipayRequest
= new AlipayTradePagePayRequest();
alipayRequest
.setReturnUrl(alipayConfig
.getReturnUrl());
alipayRequest
.setNotifyUrl(alipayConfig
.getNotifyUrl());
alipayRequest
.setBizContent("{" + " \"out_trade_no\":\"" + orderNo
+ "\","
+ " \"product_code\":\"FAST_INSTANT_TRADE_PAY\"," + " \"total_amount\":\""
+ order
.getPayment().add(order
.getPostage()) + "\"," + " \"subject\":\"" + "欢迎使用睿乐购商城" + "\","
+ " \"body\":\"" + "遇到问题,客服7*24为你解答问题" + "\","
+ " \"passback_params\":\"merchantBizType%3d3C%26merchantBizNo%3d2016010101111\","
+ " \"extend_params\":{" + " \"sys_service_provider_id\":\"2088511833207846\"" + " }" + " }");
String form
= "";
try {
form
= alipayClient
.pageExecute(alipayRequest
).getBody();
} catch (AlipayApiException e
) {
Pay pay
= new Pay(order
.getUser_id(),orderNo
,"1");
pay
.setPlatform_status("1");
payService
.addPay(pay
);
log
.debug("支付失败");
}
response
.setContentType("text/html;charset=" + alipayConfig
.getCharset());
response
.getWriter().write(form
);
response
.getWriter().flush();
response
.getWriter().close();
}
@RequestMapping("/pay/return")
public void paySuccess(String out_trade_no
,HttpServletResponse response
,HttpServletRequest request
) throws Exception
{
Map
<String, String> map
= new HashMap<String, String>();
Map
<String
, String
[]> requestParams
= request
.getParameterMap();
for (Iterator
<String> iter
= requestParams
.keySet().iterator(); iter
.hasNext(); ) {
String name
= iter
.next();
String
[] values
= requestParams
.get(name
);
String valueStr
= "";
for (int i
= 0; i
< values
.length
; i
++) {
valueStr
= (i
== values
.length
- 1) ? valueStr
+ values
[i
] : valueStr
+ values
[i
] + ",";
}
map
.put(name
, valueStr
);
}
Orders order
= orderService
.getOrderByOrderNo(out_trade_no
);
Pay pay
= new Pay(order
.getUser_id(),out_trade_no
,"1");
boolean signVerified
= false;
try {
signVerified
= AlipaySignature
.rsaCheckV1(map
, alipayConfig
.getAlipayPublicKey(),
alipayConfig
.getCharset(), alipayConfig
.getSignType());
} catch (com.alipay.api.AlipayApiException e
) {
log
.info("支付回调异常",e
);
pay
.setPlatform_status("1");
payService
.addPay(pay
);
response
.sendRedirect("/rlg/payFail.html");
}
if (signVerified
) {
orderService
.payByOrderNo(out_trade_no
);
payService
.addPay(pay
);
response
.sendRedirect("http://localhost:8082/rlg/paySuccess.html");
} else {
log
.info("验证失败");
pay
.setPlatform_status("1");
payService
.addPay(pay
);
response
.sendRedirect("http://localhost:8082/rlg/payFail.html");
}
}
@RequestMapping("/pay/notify")
public void payNotify(String out_trade_no
,HttpServletResponse response
,HttpServletRequest request
)throws Exception
{
Map
<String, String> map
= new HashMap<String, String>();
Map
<String
, String
[]> requestParams
= request
.getParameterMap();
for (Iterator
<String> iter
= requestParams
.keySet().iterator(); iter
.hasNext(); ) {
String name
= iter
.next();
String
[] values
= requestParams
.get(name
);
String valueStr
= "";
for (int i
= 0; i
< values
.length
; i
++) {
valueStr
= (i
== values
.length
- 1) ? valueStr
+ values
[i
] : valueStr
+ values
[i
] + ",";
}
map
.put(name
, valueStr
);
}
Orders order
= orderService
.getOrderByOrderNo(out_trade_no
);
Pay pay
= new Pay(order
.getUser_id(),out_trade_no
,"1");
boolean signVerified
= false;
try {
signVerified
= AlipaySignature
.rsaCheckV1(map
, alipayConfig
.getAlipayPublicKey(),
alipayConfig
.getCharset(), alipayConfig
.getSignType());
} catch (com.alipay.api.AlipayApiException e
) {
log
.info("支付回调异常",e
);
pay
.setPlatform_status("1");
payService
.addPay(pay
);
response
.sendRedirect("/rlg/payFail.html");
}
if (signVerified
) {
orderService
.payByOrderNo(out_trade_no
);
payService
.addPay(pay
);
response
.sendRedirect("/rlg/paySuccess.html");
} else {
log
.info("验证失败");
pay
.setPlatform_status("1");
payService
.addPay(pay
);
response
.sendRedirect("/rlg/payFail.html");
}
}
}
回调参数实例
{
"charset":"utf-8",
"out_trade_no":"15936564583112",
"method":"alipay.trade.page.pay.return",
"total_amount":"112.00",
"sign":"jIG9ctLKjlGW2z7v2%2FF1fhXFGwtFsidrqZzNENykIu804IvQFKzqw%2FG7TfKQE3NHNlsYaJ4FuQEUM45mK8J2sTIJy4gXDnLCsFEXwsycSP8GSWf3Jp4dCLEPteI%2F20wETa8Mqx0ySYliEcywOxc33AJTHMMKf4x5bjS3ccvqx%2BrJGLeHE8NrX%2FZEBp%2Ba1tQ7O2CRVv9ADiAMfoyyAQ47laiP5HnGKVa%2FHuBwTd%2FnLxE8dOH%2BtZp1w1MDKbm0D8Cn784gctmxGZAWUmiE3Y7U%2Bn1VNCQlSF7Oo6cd63ftJ2QJBedOWpH3RrXtGPnJlIFruAnFDUt%2F3lWv9N2qtZd9hQ%3D%3D",
"trade_no":"2020070222001435400500635866",
"auth_app_id":"2016102700771189",
"version":"1.0",
"app_id":"2016102700771189",
"sign_type":"RSA2",
"seller_id":"2088102181269576",
"timestamp":"2020-07-02+10%3A20%3A38"
}
集成Swagger2文档
访问地址
集成admin
SpringBoot - 使用Spring Boot Admin进行服务监控教程1(监控信息可视化)
参考文档
JS之document.cookie详解以及$.cookie的使用MySQL<表单&集合查询>4种方法让SpringMVC接收多个对象常用的swagger2 注解说明文档MySQL 安装Swagger2 最全注解说明Swagger注解-@ApiModel 和 @ApiModelPropertySpring Boot使用支付宝支付用springboot对接支付宝支付接口的详细开发步骤总结Java支付宝支付开发流程与原理【沙箱环境】【分布式事务解决方案】session、cookie与“记住我的登录状态”的功能的实现