jwt的登錄驗證
1.JwtProperties(jwt的基本配置項):
@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
public class JwtProperties {
/**
* 管理端員工生成jwt令牌相關配置
*/
private String adminSecretKey;
private long adminTtl;
private String adminTokenName;
/**
* 用户端微信用户生成jwt令牌相關配置
*/
private String userSecretKey;
private long userTtl;
private String userTokenName;
}
在配置文件中設置好
sky:
jwt:
# 設置jwt簽名加密時使用的秘鑰
admin-secret-key: itcast
# 設置jwt過期時間
admin-ttl: 7200000
# 設置前端傳遞過來的令牌名稱
admin-token-name: token
user-secret-key: itheima
user-ttl: 7200000
user-token-name: authentication
2.JwtTokenInterceptor(jwt的攔截器)
/**
* jwt令牌校驗的攔截器
*/
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/**
* 校驗jwt
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("當前線程的id"+ Thread.currentThread().getId());
//判斷當前攔截到的是Controller的方法還是其他資源
if (!(handler instanceof HandlerMethod)) {
//當前攔截到的不是動態方法,直接放行
return true;
}
//1、從請求頭中獲取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校驗令牌
try {
log.info("jwt校驗:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
log.info("當前員工id:", empId);
BaseContext.setCurrentId(empId);
//3、通過,放行
return true;
} catch (Exception ex) {
//4、不通過,響應401狀態碼
response.setStatus(401);
return false;
}
}
}
JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
這行代碼的作用是驗證並解析JWT令牌:
JwtUtil.parseJWT():調用JWT工具類的解析方法,該方法會驗證令牌的簽名有效性並解碼內容 。jwtProperties.getAdminSecretKey():獲取用於驗證簽名的密鑰,這個密鑰必須與生成令牌時使用的密鑰一致 。token:待解析的JWT令牌字符串,通常從HTTP請求頭中獲取。Claims claims:解析成功後返回的聲明對象,包含了JWT負載(Payload)中的所有數據 。Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());- 這行代碼從解析後的聲明中提取具體的用户標識信息:
claims.get(JwtClaimsConstant.EMP_ID):從Claims對象中獲取鍵為EMP_ID的聲明值,這通常是在用户登錄生成JWT時存入的員工ID 。.toString():將獲取到的對象轉換為字符串,確保類型安全。Long.valueOf():把字符串轉換為Long類型的員工ID,便於後續在業務中使用。
其中的parseJWT是token解密:
/**
* Token解密
*
* @param secretKey jwt秘鑰 此秘鑰一定要保留好在服務端, 不能暴露出去, 否則sign就可以被偽造, 如果對接多個客户端建議改造成多個
* @param token 加密後的token
* @return
*/
public static Claims parseJWT(String secretKey, String token) {
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 設置簽名的秘鑰
.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
// 設置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
生成jwt:
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 指定簽名的時候使用的簽名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的時間
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 設置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有聲明,一定要先設置這個自己創建的私有的聲明,這個是給builder的claim賦值,一旦寫在標準的聲明賦值之後,就是覆蓋了那些標準的聲明的
.setClaims(claims)
// 設置簽名使用的簽名算法和簽名使用的秘鑰
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 設置過期時間
.setExpiration(exp);
return builder.compact();
}
3.在代碼中使用jwt:
@PostMapping("/login")
@ApiOperation(value = "員工登錄")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
log.info("員工登錄:{}", employeeLoginDTO);
Employee employee = employeeService.login(employeeLoginDTO);
//登錄成功後,生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
.id(employee.getId())
.userName(employee.getUsername())
.name(employee.getName())
.token(token)
.build();
return Result.success(employeeLoginVO);
}
在webConfiguration中註冊自定義攔截器:
/**
* 註冊自定義攔截器
*
* @param registry
*/
protected void addInterceptors(InterceptorRegistry registry) {
log.info("開始註冊自定義攔截器...");
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/employee/login");
registry.addInterceptor(jwtTokenUserInterceptor)
.addPathPatterns("/user/**")
.excludePathPatterns("/user/user/login")
.excludePathPatterns("/user/shop/status");
}
新增菜品
在新增菜品中用到了集合遍歷,下面這三種遍歷效果一樣
傳統 for 循環
for (DishFlavor flavor : flavors) { flavor.setDishId(dishId); }
語法冗長,但邏輯清晰
Stream API
flavors.stream().peek(flavor -> flavor.setDishId(dishId)).collect(...)
適合鏈式操作,但需注意副作用(如修改原集合)
peek的作用
對流中的每個 flavor對象執行 setDishId(dishId)操作,為每個口味對象設置關聯的菜品 ID。
關鍵點:peek是中間操作,不會終止流,需配合 collect等終止操作觸發執行。
鏈式操作流程
•
流生成:flavors.stream()將集合轉為流。
•
副作用操作:peek修改元素屬性(設置 dishId)。
•
終止操作:collect(...)收集結果(如轉 List或 Map)。
方法引用
flavors.forEach(flavor -> flavor.setDishId(dishId));
與 Lambda 類似,但更簡潔
在新增菜品的時候的xml語句
1.插入數據,並且設置自增鍵
核心機制
- 數據庫自動生成主鍵 當數據庫表的主鍵字段設置為 自增(AUTO_INCREMENT) 時(如 MySQL 的
id INT AUTO_INCREMENT),執行INSERT語句時數據庫會自動生成唯一的主鍵值。 無需手動插入:你不需要在 SQL 中顯式為id字段賦值(如id=#{id}),數據庫會自動填充。 - MyBatis 回填主鍵到實體類
useGeneratedKeys="true":啓用 MyBatis 的 主鍵回填功能,通過 JDBC 的getGeneratedKeys()方法獲取數據庫生成的主鍵。keyProperty="id":將獲取到的主鍵值賦給 Java 對象的id屬性(需與實體類屬性名一致)。
<!-- useGeneratedKeys:true 表示獲取主鍵值
keyProperty="id" 表示將主鍵值賦給id屬性-->
insert into dish
(status, name, category_id, price, image, description, create_time, update_time, create_user,update_user) values
(#{status}, #{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime},#{createUser}, #{updateUser})
2:批量插入數據
collection="flavors"
功能:指定要迭代的集合來源
item="dishFlavor"
功能:定義集合中每個元素的別名。
separator=","
功能:指定元素之間的分隔符。
insert into dish_flavor(dish_id, name, value) values
(#{dishFlavor.dishId},#{dishFlavor.name},#{dishFlavor.value})
菜品的分頁查詢
除了DTO和VO外,分頁查詢還需要的是一個PageResult來獲取一個總的記錄數和當前頁的數據集合:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {
private long total; //總記錄數
private List records; //當前頁數據集合
}
@GetMapping("/page")
@ApiOperation("菜品分頁查詢")
public Result<PageResult>page (DishPageQueryDTO dishPageQueryDTO) {
log.info("菜品分頁查詢");
PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
return Result.success(pageResult) ;
}
實際實現分頁查詢:
PageHelper.startPage:在分頁查詢中使用到了PageHelper.startPage來設置分頁參數並啓動分頁攔截器。
分頁參數即當前頁碼和當前要返回多少數據,這是用前端給我們的DTO中獲取的
Page:這是內置的類型,用於分頁,含有方法來幫助獲得總記錄數和數據集合。
public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
return new PageResult(page.getTotal(),page.getResult());
}
查詢的xml語句:
作用:
- 查詢
dish表所有字段(d.*)和category表的name字段(別名categoryName)。 - 左外連接:即使
dish表的category_id在category表中無匹配記錄,仍會返回dish數據(categoryName為NULL)。
<select id="pageQuery" resultType="com.sky.vo.DishVO">
select d.* , c.name as categoryName from dish d left outer join category c on d.category_id = c.id
<where>
<if test="name != null">
and d.name like concat('%',#{name},'%')
</if>
<if test="categoryId != null">
and d.category_id = #{categoryId}
</if>
<if test="status != null">
and d.status = #{status}
</if>
</where>
order by d.create_time desc
</select>
刪除菜品
測功能較為簡單
單個刪除:
@Delete("delete from dish where id = #{id}")
批量刪除:
open和close是在最後的結果上加上左括號和右括號
<delete id="deleteByDishIds">
delete from dish_flavor where dish_id in
<foreach collection="dishIds" item="dishids" separator="," open="(" close=")">
#{dishids}
</foreach>
</delete>
修改菜品
正常的修改菜品
<update id="update">
update dish
<set>
<if test="name != null">name = #{name}, </if>
<if test="categoryId != null">category_id = #{categoryId}, </if>
<if test="price != null">price = #{price}, </if>
<if test="image != null">image = #{image}, </if>
<if test="description != null">description = #{description}, </if>
<if test="status != null">status = #{status}, </if>
<if test="updateTime != null">update_time = #{updateTime}, </if>
<if test="updateUser != null">update_user = #{updateUser}, </if>
</set>
where id = #{id}
</update>
在修改對應的口味的時候,執行刪除和插入口味的操作
Redis
在java中使用redis
1.導入Spring Data Redis 的maven座標
2.配置Redis數據源
3.編寫配置類,創建RedisTemplate對象
4.通過RedisTemplate對象操作Redis
利用RedisTemplate對象模板來進行編寫redis:
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 設置Key的序列化器
template.setKeySerializer(new StringRedisSerializer());
// 設置Value的序列化器為JSON序列化器
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 設置Hash Key的序列化器
template.setHashKeySerializer(new StringRedisSerializer());
// 設置Hash Value的序列化器
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
為什麼要設置序列化器?
答:
RedisTemplate默認使用 JDK 序列化,這會導致存儲在 Redis 裏的 key 和 value 是二進制的、不可讀的格式(類似 \xac\xed\x00\x05t\x00\x03city)。
通過設置 KeySerializer為 StringRedisSerializer,確保了所有 Redis 的 key 都會以可讀的字符串形式存儲(例如,直接就是 "city"),這在命令行調試或可視化工具中查看時非常清晰。
操作幾種類型的數據:
ValueOperations valueOperations = redisTemplate.opsForValue();
HashOperations hashOperations = redisTemplate.opsForHash();
ListOperations listOperations = redisTemplate.opsForList();
SetOperations setOperations = redisTemplate.opsForSet();
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
/**
* 操作字符串類型數據
*/
@Test
public void testString() {
//set
redisTemplate.opsForValue().set("city", "BEIJING");
//get
String city = (String) redisTemplate.opsForValue().get("city");
System.out.println(city);
//setex
redisTemplate.opsForValue().set("province", "shanxi", 2, TimeUnit.MINUTES);
//setnx
redisTemplate.opsForValue().setIfAbsent("look", "1");
redisTemplate.opsForValue().setIfAbsent("look", "2");
}
/**
* 操作哈希類型的數據
*/
@Test
public void testHash() {
//hset het hdel hkeys hvals
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.put("100", "name", "tom");
hashOperations.put("100", "age", "20");
String name = (String) hashOperations.get("100", "age");
System.out.println(name);
Set keys = hashOperations.keys("100");
System.out.println(keys);
List values = hashOperations.values("100");
System.out.println(values);
hashOperations.delete("100", "age");
}
/**
* 操作列表類型數據
*/
//Lpush Lrange rpop Llen
@Test
public void testList() {
ListOperations listOperations = redisTemplate.opsForList();
listOperations.leftPushAll("mylist", "a", "b", "c");
listOperations.leftPush("mylist", "d");
List mylist = listOperations.range("mylist", 0, -1);
System.out.println(mylist);
listOperations.rightPop("mylist");
Long size = listOperations.size("mylist");
System.out.println(size);
}
@Test
public void testSet() {
//sadd smebers scard sinter sunion srem
SetOperations setOperations = redisTemplate.opsForSet();
setOperations.add("set1", "a", "b", "c", "d");
setOperations.add("set2", "b", "c", "d", "e");
Set members = setOperations.members("set1");
System.out.println(members);
Long size = setOperations.size("set1");
System.out.println(size);
Set intersection = setOperations.intersect("set1", "set2");
System.out.println(intersection);
Set union = setOperations.union("set1", "set2");
System.out.println(union);
setOperations.remove("set1", "a", "b");
}
/**
* 操作有序集合
*/
@Test
public void testZSet() {
//zadd zrange zincrby zrem
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
zSetOperations.add("zset1", "a", 10);
zSetOperations.add("zset1", "b", 20);
zSetOperations.add("zset1", "c", 30);
Set zset1 = zSetOperations.range("zset1", 0, -1);
System.out.println(zset1);
zSetOperations.incrementScore("zset1", "c", 1);
zSetOperations.remove("zset1", "a");
}
/**
* 通用命令操作
*/
//keys exists type del
@Test
public void testCommon() {
Set keys = redisTemplate.keys("*");
System.out.println(keys);
Boolean name = redisTemplate.hasKey("name");
Boolean age = redisTemplate.hasKey("set1");
for (Object key : keys) {
DataType type = redisTemplate.type(key);
System.out.println(type.name());
}
redisTemplate.delete("mylist");
}
HttpClient
作用:1.發送http請求
2.接受響應數據
代碼解釋:
CloseableHttpClient httpClient = HttpClients.createDefault();
創建HTTP客户端實例。這是發送請求的基礎,類似於打開一個瀏覽器
HttpClients.createDefault()會創建一個具有默認配置的HTTP客户端,該客户端支持連接複用(連接池),能有效提升性能
HttpGet httpGet = new HttpGet("http://localhost:8080/...");
構建GET請求對象。指定了請求的URL(這裏是一個本地服務的地址)和HTTP方法(GET)
HttpGet對象封裝了請求的所有信息,包括URL、請求頭、參數等。你可以通過 setHeader方法為其添加請求頭
CloseableHttpResponse response = httpClient.execute(httpGet);
發送請求並獲取響應。這是執行網絡調用的核心步驟
httpClient.execute(...)方法會同步地發送請求,並返回一個 CloseableHttpResponse對象,該對象包含了服務器返回的全部信息
int statusCode = response.getStatusLine().getStatusCode();
從響應中獲取HTTP狀態碼(如200表示成功,404表示未找到等)
狀態碼是HTTP協議規定的一部分,位於響應行的第一行,通過 StatusLine對象獲取
HttpEntity entity = response.getEntity();
String body = EntityUtils.toString(entity);
從響應中提取響應體內容。響應體是服務器返回的實際數據(如JSON、HTML等)
HttpEntity代表了HTTP消息的實體內容(如請求體或響應體)。EntityUtils.toString(...)是一個工具方法,將實體內容流轉換為字符串
response.close();
httpClient.close();
關閉連接,釋放資源。這非常重要,可以防止資源泄漏
GET方式請求:
@Test
public void testGet() throws IOException {
//創建httpclient對象
CloseableHttpClient httpClient = HttpClients.createDefault();
//創建請求對象
HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");
//發送請求並接收響應結果
CloseableHttpResponse response = httpClient.execute(httpGet);
//獲取服務端返回的狀態碼
int statusCode = response.getStatusLine().getStatusCode();
System.out.println(statusCode);
HttpEntity entity = response.getEntity();
String body = EntityUtils.toString(entity);
System.out.println("服務端返回的數據是"+body);
//關閉資源
response.close();
httpClient.close();
}
1. 接收原始數據
CloseableHttpResponse
代表完整的HTTP響應,其內部通過 socket 連接接收來自服務器的原始字節流。這是最底層的數據形式。
優點:完整保留了服務器發送的原始信息。
缺點:對開發者不友好,無法直接讀取和操作。
2. 協議層封裝
HttpEntity entity = response.getEntity();
HttpEntity是一個封裝對象,它包裹了原始的字節流 (InputStream),並提供了訪問響應體(內容字節)、內容類型 (Content-Type) 和內容長度 (Content-Length) 等元數據的方法。它將雜亂的字節流包裝成一個結構化的對象。
優點:提供了一個標準的接口來訪問響應內容和元數據,屏蔽了底層字節流的複雜性。
缺點:內容本身仍是字節,需要進一步處理。
3. 應用層解碼
String body = EntityUtils.toString(entity);
這是最關鍵的一步!EntityUtils.toString()方法會做兩件事:
- 從
HttpEntity中讀取字節流。
2. 根據元數據(如Content-Type頭中的charset=UTF-8)或默認設置,將字節流解碼成字符串。這一步將機器理解的字節轉換成了人類和程序容易處理的文本。
post方式請求:
@Test
public void testPost() throws IOException, JSONException {
//創建httpClient對象
CloseableHttpClient httpClient = HttpClients.createDefault();
//創建請求對象
HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");
JSONObject jsonObject = new JSONObject();
jsonObject.put("username","admin");
jsonObject.put("password","123456");
StringEntity stringEntity = new StringEntity(jsonObject.toString());
//指定請求編碼方式
stringEntity.setContentEncoding("UTF-8");
stringEntity.setContentType("application/json");
httpPost.setEntity(stringEntity);
//發送請求
CloseableHttpResponse response = httpClient.execute(httpPost);
//解析返回結果
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("響應碼為:"+statusCode);
HttpEntity entity = response.getEntity();
String body = EntityUtils.toString(entity);
System.out.println("響應數據為"+body);
//關閉資源
response.close();
httpClient.close();
}
代碼解析:
CloseableHttpClient httpClient = HttpClients.createDefault();
創建HTTP客户端實例。這是發送所有請求的基礎,類似於打開一個瀏覽器
HttpPost httpPost = new HttpPost("http://localhost...");
創建POST請求對象,並設定請求的目標URL(這裏是一個本地登錄接口)
JSONObject jsonObject = new JSONObject();
jsonObject.put("username","admin");...
構建JSON格式的請求參數。這裏將一個包含用户名(admin)和密碼(123456)的Java對象轉換為JSON字符串,作為請求體發送
StringEntity stringEntity = new StringEntity(jsonObject.toString());
stringEntity.setContentEncoding("UTF-8");
stringEntity.setContentType("application/json");
httpPost.setEntity(stringEntity);
設置請求體。將JSON字符串包裝成StringEntity,並明確指定其內容編碼為UTF-8,內容類型為application/json,然後將其放入POST請求中
CloseableHttpResponse response = httpClient.execute(httpPost);
執行請求。客户端將攜帶參數的請求發送至服務器,並獲取一個包含所有返回信息的響應對象
int statusCode = response.getStatusLine().getStatusCode();
String body = EntityUtils.toString(entity);
解析響應。從響應中提取HTTP狀態碼(如200成功)和響應體(服務器返回的實際數據,如登錄結果)
response.close();
httpClient.close();
關閉連接,釋放資源。這是一個好習慣,可以防止資源泄漏
(為什麼將其包裝成StringEnity?
答:因為HTTP請求體(Body)傳輸的是字節流(byte stream),而不是Java對象。直接將JSON對象(如JSONObject)放進去是不可能的,必須有一個“轉換器”將它變成符合HTTP協議標準的格式。StringEntity就是這個關鍵的轉換器。
下面這個表格清晰地對比了兩種方式的本質區別:
|
特性
|
直接放入JSON對象 (不可行)
|
使用StringEntity (標準做法)
|
|
HTTP協議要求 |
請求體必須是字節流
|
將字符串轉換為字節流,符合協議要求 |
|
數據格式 |
Java內存中的對象,無法直接傳輸
|
字符串(String),是字節流的直接來源 |
|
內容類型(Content-Type) |
無法自動設置,服務器無法識別數據格式
|
可明確設置(如 |
|
字符編碼 |
無法控制,可能導致亂碼
|
可指定編碼(如 |
)
微信登錄
配置微信登陸的設置項:
配置為微信用户生成jwt令牌時使用的配置項:
DTO:
VO:
Controller:
@RestController
@RequestMapping("/user/user")
@Slf4j
@Api(tags = "C端用户接口")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtProperties jwtProperties;
/**
* 微信登陸
* @param userLoginDTO
* @return
*/
@PostMapping("/login")
@ApiOperation("微信登錄")
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO) {
log.info("微信用户登錄:{}",userLoginDTO.getCode());
//微信登錄
User user = userService.wxlogin(userLoginDTO);
//微信用户生成jwt令牌
Map<String,Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.USER_ID,user.getId());
String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(),jwtProperties.getUserTtl(),claims);
UserLoginVO userLoginVO = UserLoginVO.builder()
.id(user.getId())
.openid(user.getOpenid())
.token(token)
.build();
return Result.success(userLoginVO);
}
}
Service:
@Service
@Slf4j
public class UserServiceImpl implements UserService {
public static final String WX_LOGIN ="https://api.weixin.qq.com/sns/jscode2session";
@Autowired
private WeChatProperties weChatProperties;
@Autowired
private UserMapper userMapper;
@Autowired
private UserController userController;
/**
* 微信登錄
* @param userLoginDTO
* @return
*/
@Override
public User wxlogin(UserLoginDTO userLoginDTO) {
// Map<String, String> map = new HashMap();
//
// map.put("appid",weChatProperties.getAppid());
// map.put("secret",weChatProperties.getSecret());
// map.put("js_code",userLoginDTO.getCode());
// map.put("grant_type","authorization_code");
// //調用微信接口服務,獲取用户的openid
// String json = HttpClientUtil.doGet(WX_LOGIN,map);
//
//
// JSONObject jsonObject = JSON.parseObject(json);
// String openid = jsonObject.getString("openid");
String openid =getOpenid(userLoginDTO.getCode());
//判斷openid是否為空?拋出異常:合法
if (openid ==null){
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
//判斷微信用户是否為外賣系統的新用户,是的話進行註冊並保存openid
User user = userMapper.getByOpenid(openid);
if (user == null){
user = User.builder()
.openid(openid)
.createTime(LocalDateTime.now())
.build();
userMapper.insert(user);
}
return user;
}
private String getOpenid(String code) {
Map<String, String> map = new HashMap();
map.put("appid",weChatProperties.getAppid());
map.put("secret",weChatProperties.getSecret());
map.put("js_code",code);
map.put("grant_type","authorization_code");
//調用微信接口服務,獲取用户的openid
String json = HttpClientUtil.doGet(WX_LOGIN,map);
JSONObject jsonObject = JSON.parseObject(json);
String openid = jsonObject.getString("openid");
return openid;
}
}
還有jwt的攔截器編寫和註冊攔截器...(和jwt登錄驗證一起統一學習)
緩存菜品
例如:在查詢菜品的時候開始是否緩存?
/**
* 根據分類id查詢菜品
*
* @param categoryId * @return
*/
@GetMapping("/list")
@ApiOperation("根據分類id查詢菜品")
public Result<List<DishVO>> list(Long categoryId) {
//構造redis中的key,規則:dish_分類id
String key = "dish_"+categoryId;
//查詢redis中是否存在菜品數據
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
if (list != null && list.size()>0) {
//如果存在,直接返回,無需查詢數據庫
return Result.success(list);
}
Dish dish = new Dish();
dish.setCategoryId(categoryId);
dish.setStatus(StatusConstant.ENABLE);//查詢起售中的菜品
//如果不存在,查詢數據庫,將查詢到的數據放入redis中
list = dishService.listWithFlavor(dish);
redisTemplate.opsForValue().set(key,list);
return Result.success(list);
}
刪除緩存操作(像在進行對菜品修改的時候,對菜品刪除的時候,它的緩存數據就需要刪除):
private void claeanCache(String pattern){
Set keys = redisTemplate.keys(pattern);
redisTemplate.delete(keys);
}
利用註解來進行緩存:
@Cacheable
- 作用:適用於查詢方法。遵循“先查緩存,沒有再執行方法”的流程。
- 用法:標註在方法上。主要指定兩個屬性:
value/cacheNames:緩存名稱(必填),可指定多個,如@Cacheable("users")或@Cacheable({"users", "admins"})。key:緩存鍵(可選)。支持 SpEL 表達式,默認為方法參數組合。如@Cacheable(value="user", key="#id")。condition:條件緩存(可選)。滿足條件才緩存,如@Cacheable(..., condition="#id > 10")。
@CachePut
- 作用:適用於更新/新增方法。總是會執行方法,並將方法返回的結果更新到緩存中。
- 用法:通常用於更新數據庫後同步更新緩存。必須和
@Cacheable使用相同的value和key,以確保更新的是同一份數據。 - 注意:與
@Cacheable最大區別在於,它從不跳過方法執行。
@CacheEvict
- 作用:適用於刪除方法。用於從緩存中移除數據。
- 用法:標註在方法上。關鍵屬性:
value/cacheNames:指定要清除的緩存名稱。key:指定要清除的緩存鍵。allEntries:是否清空整個緩存區域(可選,默認為false)。若為true,則忽略key,清除value下的所有緩存。beforeInvocation:清除操作在方法調用前還是調用後執行(可選,默認為false即方法調用後執行)。設置為true可避免方法執行異常導致緩存未清除。
微信支付:
Spring Task定時任務編寫:
@Scheduled(cron = "0 0 1 * * ?")//每天凌晨一點觸發一次
/**
* 處理一直處於派送中的訂單
*/
@Scheduled(cron = "0 0 1 * * ?")//每天凌晨一點觸發一次
public void processDeliveryOrder(){
log.info("定時處理處於派送中的訂單:{}", LocalDateTime.now());
List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS,LocalDateTime.now().plusMinutes(-60));
for (Orders orders : ordersList) {
orders.setStatus(Orders.COMPLETED);
orderMapper.update(orders);
}
}
}
利用此註解進行定時任務觸發。
Websocket:
實現步驟:
直接使用websocket.html頁面作為WebSocket客户端
導入WebSocket的maven座標
導入WebSocket服務端組件WebSocketServer,用於和客户端通信
導入配置類WebSocketConfiguration,註冊WebSocket的服務端組件
導入定時任務類WebSocketTask,定時向客户端推送數據
WebSocketServer:
/**
* WebSocket服務
*/
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
//存放會話對象
private static Map<String, Session> sessionMap = new HashMap();
/**
* 連接建立成功調用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
System.out.println("客户端:" + sid + "建立連接");
sessionMap.put(sid, session);
}
/**
* 收到客户端消息後調用的方法
*
* @param message 客户端發送過來的消息
*/
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
System.out.println("收到來自客户端:" + sid + "的信息:" + message);
}
/**
* 連接關閉調用的方法
*
* @param sid
*/
@OnClose
public void onClose(@PathParam("sid") String sid) {
System.out.println("連接斷開:" + sid);
sessionMap.remove(sid);
}
/**
* 羣發
*
* @param message
*/
public void sendToAllClient(String message) {
Collection<Session> sessions = sessionMap.values();
for (Session session : sessions) {
try {
//服務器向客户端發送消息
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
APch@ServerEndpoint("/ws/{sid}")
將該類聲明為一個WebSocket服務端點,客户端通過 ws://地址:端口/ws/客户端唯一標識連接
@Component
表明這是一個Spring管理的Bean,通常需要配合ServerEndpointExporter配置才能生效
sessionMap
一個靜態的Map,核心設計,用於全局保存所有已連接的客户端會話(Session)及其標識(sid)
@OnOpen
標註客户端連接成功時調用的方法,用於初始化操作
@OnMessage
標註收到客户端消息時調用的方法,用於處理業務邏輯
@OnClose
標註連接關閉時調用的方法,用於清理資源
@PathParam("sid")
用於從連接路徑中提取變量sid的值,作為客户端的唯一標識
sendToAllClient
一個自定義的羣發方法,用於向所有連接的客户端推送消息
代碼片段
角色與作用
通俗理解
session
代表一次獨立的 WebSocket 連接
好比是和一個朋友建立的專屬電話線路
.getBasicRemote()
獲取一個同步的消息發送對象
選擇用普通電話模式交流(説完一句等對方迴應)
.sendText(message)
同步地發送文本消息
在電話裏説出一句話,並等待對方聽到
WebSocketConfiguration
/**
* WebSocket配置類,用於註冊WebSocket的Bean
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
測試任務:
WebSocketTask
@Component
public class WebSocketTask {
@Autowired
private WebSocketServer webSocketServer;
/**
* 通過WebSocket每隔5秒向客户端發送消息
*/
@Scheduled(cron = "0/5 * * * * ?")
public void sendMessageToClient() {
webSocketServer.sendToAllClient("這是來自服務端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
}
}
Apache ECharts
/**
* 訂單統計
* @param begin
* @param end
* @return
*/
@GetMapping("/ordersStatistics")
@ApiOperation("訂單數據統計")
public Result<OrderReportVO> ordersStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd")LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd")LocalDate end)
{
log.info("訂單數據統計:{},{}",begin,end);
return Result.success(reportService.getOrderStatistics(begin,end));
}
@Override
public OrderReportVO getOrderStatistics(LocalDate begin, LocalDate end) {
List<LocalDate> dateList = new ArrayList<>();
dateList.add(begin);
while(!begin.equals(end)){
begin = begin.plusDays(1);
dateList.add(begin);
}
List<Integer> orderCountList = new ArrayList<>();
List<Integer> ValidOrderCountList = new ArrayList<>();
Integer TotalOrderCount = 0;
Integer ValidOrderCount = 0;
for (LocalDate date : dateList) {
LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
Map map = new HashMap();
map.put("begin", beginTime);
map.put("end", endTime);
map.put("status", Orders.COMPLETED);
Integer totalOrder = userMapper.countOrderByMap(map);
TotalOrderCount = TotalOrderCount + totalOrder;
orderCountList.add(totalOrder);
Integer validOrder = userMapper.countOrderValidByMap(map);
ValidOrderCount = ValidOrderCount + validOrder;
ValidOrderCountList.add(validOrder);
}
Double OrderCompletionRate = ValidOrderCount.doubleValue()/TotalOrderCount.doubleValue();
return OrderReportVO.builder()
.dateList(StringUtils.join(dateList, ","))
.orderCountList(StringUtils.join(orderCountList, ","))
.validOrderCountList(StringUtils.join(ValidOrderCountList, ","))
.totalOrderCount(TotalOrderCount)
.validOrderCount(ValidOrderCount)
.orderCompletionRate(OrderCompletionRate)
.build();
}
<select id="countOrderByMap" resultType="java.lang.Integer">
select count(id) from orders
<where>
<if test="begin != null">
and order_time >#{begin}
</if>
<if test="end != null">
and order_time <#{end}
</if>
</where>
</select>
Apache POI:
@Override
public void exportBusinessData(HttpServletResponse response) {
//1.查詢數據庫,獲取營業數據————查詢銷量30天的運營數據
LocalDate dateBegin = LocalDate.now().minusDays(30);
LocalDate dateEnd = LocalDate.now().minusDays(1);
LocalDateTime beginTime = LocalDateTime.of(dateBegin, LocalTime.MIN);
LocalDateTime endTime = LocalDateTime.of(dateEnd, LocalTime.MAX);
BusinessDataVO businessDataVO = workspaceService.getBusinessData(beginTime, endTime);
//2.通過poi將數據寫入excel文件
InputStream in = this.getClass().getClassLoader().getResourceAsStream("template/運營數據報表模板.xlsx");
try {
//基於模板文件創建一個心的excel文件
XSSFWorkbook excel = new XSSFWorkbook(in);
XSSFSheet sheet = excel.getSheet("Sheet1");
//填充數據--時間
sheet.getRow(1).getCell(1).setCellValue("時間:"+dateBegin+"至:"+dateEnd);
sheet.getRow(3).getCell(2).setCellValue(businessDataVO.getTurnover());
sheet.getRow(3).getCell(4).setCellValue(businessDataVO.getOrderCompletionRate());
sheet.getRow(3).getCell(6).setCellValue(businessDataVO.getNewUsers());
//獲得第五行
XSSFRow row = sheet.getRow(4);
row.getCell(2).setCellValue(businessDataVO.getValidOrderCount());
row.getCell(4).setCellValue(businessDataVO.getUnitPrice());
//填充明細數據
for (int i = 0; i < 30; i++) {
LocalDate date = dateBegin.plusDays(i);
//查詢某一天的營業數據
BusinessDataVO businessData = workspaceService.getBusinessData(LocalDateTime.of(date, LocalTime.MIN), LocalDateTime.of(date, LocalTime.MAX));
//獲得行
row = sheet.getRow(7 + i);
//獲得單元格
row.getCell(1).setCellValue(date.toString());
row.getCell(2).setCellValue(businessData.getTurnover());
row.getCell(3).setCellValue(businessData.getValidOrderCount());
row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
row.getCell(5).setCellValue(businessData.getUnitPrice());
row.getCell(6).setCellValue(businessData.getNewUsers());
}
//3.通過輸出流將excel文件下載到客户端瀏覽器
ServletOutputStream out = response.getOutputStream();
excel.write(out);
//關閉資源
out.close();
excel.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}