抽獎系統(從0-1)(上)

hu項目的開發流程介紹

????????1. 項目啟動階段?

????????? 項?概述:介紹項?的背景、?標和預期成果。

????????? 團隊組建:建跨職能團隊,包括產品經理、UI/UX 設計師、開發?員、測試?員等。

????????? ??定義:明確團隊中各個??的職責和?作內容。

????????2. 需求分析與規劃

????????? 需求收集:如何通過訪談、問卷、競品分析等?法收集??需求.(一般都是產品經理)

????????? 需求整理:講解需求整理的技巧,如需求分類、優先級排序等。

????????? 需求?檔撰寫:指導如何編寫清晰、完整的需求?檔。

????????3. UI/UX設計與原型制作

????????? UI設計基礎:介紹 UI 設計的基本原則和流?趨勢。

????????? 原型?具使?:演?如何使?Axure、Sketch等?具制作交互原型。

????????? ??測試:講解如何進???測試,收集反饋并優化設計。

????????4. 需求拆分與任務分配

????????? 需求拆分:如何將?的需求拆分為?的、可管理的任務(開發開始介入:代碼,業務功能的拆分)

????????? 任務估算:如何估算任務的?作量和時間。

????????5. 代碼開發與技術實現

]????????? 技術選型:講解如何根據項?需求選擇合適的技術棧。

? ? ? ? ?? 代碼規范:強調代碼的規范性、可讀性和可維護性。

????????? 版本控制:介紹版本控制?具如 Git 的使?和管理。

????????6. 測試與質量保證

????????? 測試策略:不同類型的測試,如單元測試、集成測試、性能測試等。

????????? 缺陷跟蹤:如何使?JIRA、Bugzilla等?具進?缺陷跟蹤和管理。

????????? ?動化測試:介紹?動化測試?具和框架,如Selenium、JUnit。

????????7. 部署與上線

????????? 部署流程:從開發環境到?產環境的部署流程。

????????? 上線準備:如何準備上線,包括安全檢查、性能優化等。

????????? 持續集成/持續部署(CI/CD):CI/CD的概念和實踐。

????????8. 項目回顧與總結

????????? 項?復盤:回顧整個項?過程,總結經驗教訓。

????????? 團隊協作:討論團隊協作中的挑戰和改進措施。

????????? 持續改進:強調持續改進的重要性,介紹如何建?反饋機制。

1. 項目介紹

? ? ? ? 大致要搞什么東西

????????模擬公司年會/活動抽獎

? ? ? ? 1. 管理人員/hr人員圈選,設置抽獎活動,設置獎品.抽獎

? ? ? ? 2. 用戶: 看結果

背景????????

????????隨著數字營銷的興起,企業越來越重視通過在線活動來吸引和留住客?。抽獎活動作為?種有效的營銷?段,能夠顯著提升??參與度和品牌曝光率。于是我們就開發了以抽獎活動作為背景的Spring Boot項?,通過這個項?提供?個全?、可靠、易于維護的抽獎平臺,該平臺將采?以下策略:

????????? 集成多種技術組件:利?MySQL、Redis、RabbitMQ等常?組件,構建?個穩定、?效、可擴展的抽獎系統。

????????? 活動、獎品與?員管理:允許管理員創建配置抽獎活動;管理獎品信息;管理?員信息。

????????? 實現狀態機管理:通過精?設計的狀態機,精確控制活動及獎品狀態的轉換,提?系統的可控性和可預測性。

????????? 保障數據?致性:通過事務管理和數據同步機制,確保數據的?致性和完整性。

????????? 加強安全性:實施安全措施,包括數據加密、??認證,保護??數據和系統安全。

????????? 降低維護成本:提供全?的?志記錄和異常處理機制,簡化問題診斷和系統維護。

????????? 提?擴展性:采?模塊化設計與設計模式的使?,提?系統的靈活性和擴展性。

涉及的功能模塊頁面展示

管理員的注冊和登錄

活動列表

點擊開始的活動,就可以開始抽獎

點擊結束的活動,會出現抽獎名單

點擊分享結果,就能夠把鏈接進行復制

新建抽獎活動

圈選獎品可以勾選獎品和數量,以及設置的獎種

點擊圈選人員,可以圈選當前參與抽獎的人

獎品管理?

翻頁功能

創建獎品

人員管理

人員列表展示

注冊用戶

需求概述

預期成果

????????提供?個功能全?、操作簡便、安全可靠的抽獎平臺。

?目標用戶

????????? 管理?員:管理?員、創建和管理抽獎活動,及發起抽獎

????????? 普通??:可查看中獎名單,若??中獎需要收到相關中獎通知。

?UI 設計

?????????《UI 界?》?檔

需求描述

????????1. 包含管理員的注冊與登錄。

????????a. 注冊包含:姓名、郵箱、?機號、密碼

????????b. 登錄包含兩種?式:

????????????????i. 電話+密碼登錄;

????????????????ii. 電話+短信登錄; 驗證碼獲取

????????????????iii. 登錄需要校驗管理員?份。

????????2. ?員管理: 管理員?持創建普通用戶, 查看用戶列表

????????a. 創建普通??:姓名,郵箱,?機號

????????b. ?員列表:?員id、姓名、?份(普通??、管理員)

????????3. 管理端?持創建獎品、獎品列表展示功能。

????????a. 創建的獎品信息包含:獎品名稱、描述、價格、獎品圖(上傳)

????????b. 獎品列表展?(可翻?):獎品id、獎品圖、獎品名、獎品描述、獎品價值(元)

????????4. 管理端?持創建活動、活動列表展示功能。

????????a. 創建的活動信息包含:

????????????????i. 活動名稱

????????????????ii. 活動描述

????????????????iii. 圈選獎品:勾選對應獎品,并設置獎品等級(??三等獎),及獎品數量 iv. 圈選?員:勾選參與抽獎?員

????????b. 活動列表展?(可翻?):

????????????????i. 活動名稱

????????????????ii. 描述

????????????????iii. 活動狀態:

????????????????1. 活動狀態為進?中:點擊 "活動進?中, 去抽獎" 按鈕跳轉抽獎?

????????????????2. 活動狀態為已完成:點擊 "活動已完成, 查看中獎名單" 按鈕跳轉抽獎?查看結果

????????5. 抽獎頁面:

????????a. 對于進?中的活動,管理員才可抽獎。

????????b. 每輪抽獎的中獎?數跟隨當前獎品數量。

????????c. 每個?只能中?次獎

????????d. 多輪抽獎,每輪抽獎有3個環節:展?獎品信息(獎品圖、份數),?名閃動,停?閃動確定中獎名單

????????????????i. 當前?展?獎品信息, 點擊‘開始抽獎’按鈕, 則跳轉??名閃動畫?

????????????????ii. ?員閃動畫?,點擊’點我確定‘按鈕,確認中獎名單。

????????????????iii. 當前?展?中獎名單, 點擊‘已抽完,下?步’按鈕, 若還有獎品未抽取, 則展?下?個獎品信息, 否則展?全部中獎名單

????????????????iv. 點擊’查看上?獎項‘按鈕,展?上?個獎品信息

? ? ? ? ? ?e. 對于抽獎過程中的異常情況,如抽獎過程中刷新??,要保證抽取成功的獎項不能重新抽取。

????????????????i. 刷新??后, 若當前獎品已抽完, 點擊"開始抽獎",則直接展?當前獎品中獎名單

????????????f. 如該抽獎活動已完成:

????????????????i. 展?所有獎項的全部中獎名單

????????????????ii. 新增"分享結果"按鈕, 點擊可復制當前?鏈接, 打開后隱藏其他按鈕, 只展?活動名稱與中獎結果, 保留"分享結果" 按鈕

????????6. 通知部分: 抽獎完成需以郵件和短信方式通知中獎者。

????????a. “Hi,xxx。恭喜你在xxx抽獎中獲得?等獎:?機。中獎時間為:xx:xx。請盡快領取您的獎品。”

????????7. 管理端涉及的所有頁面, 包括抽獎頁,需強制管理員登錄后?可訪問。

????????a. 未登錄強制跳轉登錄??

系統設計

系統架構??

? ?? ? ?? 前端:使?JavaScript管理各界?的動態性,使?AJAX技術從后端API獲取數據。

????????? 后端:采?Spring Boot3構建后端應?,實現業務邏輯。

????????? 數據庫:使?MySQL作為主數據庫,存儲??數據和活動信息。

????????? 緩存:使?Redis作為緩存層,減少數據庫訪問次數。

????????? 消息隊列:使?RabbitMQ處理異步任務,如處理抽獎?為。

????????? 日志與安全:使?JWT進???認證,使?SLF4J+logback完成?志。

項目環境

????????? 編程語?:Java(后端),JavaScript(前端)。

????????? 開發?具包:JDK 17?

????????? 后端框架:Spring Boot3。

????????? 數據庫:MySQL。

????????? 緩存:Redis。(在服務器安裝)

????????? 消息隊列:RabbitMQ。(在服務器安裝)

????????? 日志:logback。

????????? 安全:JWT + 加密。

業務功能模塊

????????? ?員業務模塊:管理員注冊、登錄,及普通??的創建。

????????? 活動業務模塊:活動管理及活動狀態管理。

????????? 獎品業務模塊:獎品管理與獎品的分配。還包括獎品圖的上傳。

????????? 通知業務模塊:發送短信、郵件等業務,例如驗證碼發送,中獎通知。

????????? 抽獎業務模塊:完成抽獎動作,以及抽獎后的結果展?。

數據庫設計

????????? 用戶表:存儲??信息,如??名、密碼、郵箱等。

????????? 活動表:存儲活動信息,如活動名稱、描述、活動狀態等。

????????? 獎品表:存儲獎品信息,如獎品名稱、獎品圖等。

????????? 活動獎品關聯表:存儲?個活動下關聯了哪些獎品。

????????? 活動用戶關聯表:存儲?個活動下設置的參與?員。

????????? 中獎記錄表:存儲?個活動的中獎名單,如活動id,獎品id,中獎者id等。

數據準備

????????建表:使? source 命令導? .sql ?件

? ? ? ? 在mysql數據庫進行建表

-- 設置客戶端與服務器之間的字符集為utf8mb4,這個字符集可以存儲任何Unicode字符。
SET NAMES utf8mb4;
-- 關閉外鍵約束檢查,這通常在創建或修改表結構時使用,以避免由于外鍵約束而導致的創建失敗。
SET FOREIGN_KEY_CHECKS = 0;drop database IF EXISTS `lottery_system`;
create DATABASE `lottery_system` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;USE `lottery_system`;-- ----------------------------
-- Table structure for activity
-- ----------------------------
drop table IF EXISTS `activity`;
create TABLE `activity`  (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主鍵',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '創建時間',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新時間',`activity_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '活動名稱',`description` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '活動描述',`status` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '活動狀態',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;
-- ENGINE = InnoDB:指定表的存儲引擎為InnoDB,這是MySQL的默認存儲引擎,支持事務、外鍵等特性。
-- AUTO_INCREMENT = 24:為自動增長的ID字段設置起始值。
-- ROW_FORMAT = DYNAMIC:設置行的存儲格式為動態,允許行隨著數據的變化而變化。-- ----------------------------
-- Table structure for activity_prize
-- ----------------------------
drop table IF EXISTS `activity_prize`;
create TABLE `activity_prize`  (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主鍵',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '創建時間',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新時間',`activity_id` bigint NOT NULL comment '活動id',`prize_id` bigint NOT NULL comment '活動關聯的獎品id',`prize_amount` bigint NOT NULL DEFAULT 1 comment '關聯獎品的數量',`prize_tiers` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '獎品等級',`status` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '活動獎品狀態',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE,UNIQUE INDEX `uk_a_p_id`(`activity_id` ASC, `prize_id` ASC) USING BTREE,INDEX `idx_activity_id`(`activity_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Table structure for activity_user
-- ----------------------------
drop table IF EXISTS `activity_user`;
create TABLE `activity_user`  (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主鍵',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '創建時間',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新時間',`activity_id` bigint NOT NULL comment '活動時間',`user_id` bigint NOT NULL comment '圈選的用戶id',`user_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '用戶名',`status` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '用戶狀態',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE,UNIQUE INDEX `uk_a_u_id`(`activity_id` ASC, `user_id` ASC) USING BTREE,INDEX `idx_activity_id`(`activity_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Table structure for prize
-- ----------------------------
drop table IF EXISTS `prize`;
create TABLE `prize`  (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主鍵',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '創建時間',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新時間',`name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '獎品名稱',`description` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL comment '獎品描述',`price` decimal(10, 2) NOT NULL comment '獎品價值',`image_url` varchar(2048) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL comment '獎品展示圖',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Table structure for user
-- ----------------------------
drop table IF EXISTS `user`;
create TABLE `user`  (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主鍵',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '創建時間',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新時間',`user_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '用戶姓名',`email` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '郵箱',`phone_number` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '手機號',`password` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL comment '登錄密碼',`identity` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '用戶身份',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE,UNIQUE INDEX `uk_email`(`email`(30) ASC) USING BTREE,UNIQUE INDEX `uk_phone_number`(`phone_number`(11) ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 39 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Table structure for winning_record
-- ----------------------------
drop table IF EXISTS `winning_record`;
create TABLE `winning_record`  (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT comment '主鍵',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '創建時間',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON update CURRENT_TIMESTAMP comment '更新時間',`activity_id` bigint NOT NULL comment '活動id',`activity_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '活動名稱',`prize_id` bigint NOT NULL comment '獎品id',`prize_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '獎品名稱',`prize_tier` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '獎品等級',`winner_id` bigint NOT NULL comment '中獎人id',`winner_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '中獎人姓名',`winner_email` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '中獎人郵箱',`winner_phone_number` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL comment '中獎人電話',`winning_time` datetime NOT NULL comment '中獎時間',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_id`(`id` ASC) USING BTREE,UNIQUE INDEX `uk_w_a_p_id`(`winner_id` ASC, `activity_id` ASC, `prize_id` ASC) USING BTREE,INDEX `idx_activity_id`(`activity_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 69 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;-- SET FOREIGN_KEY_CHECKS = 1;:在腳本的最后,重新開啟外鍵約束檢查。
SET FOREIGN_KEY_CHECKS = 1;

? ? ? ? 或者我們把它放在記事本,然后命名,之后啟動mysql,使用source

?然后我們執行這行命令,直接就運行我們的寫好的sql腳本(剛剛保存的文件注意:路徑不能存在中?!!)
?

數據庫表ER圖

安全設計

????????? 用戶登錄?份驗證:使? JWT 進????份驗證。需強制??在某些??必須進?登錄操作。

????????? 加密:敏感信息數據加密。例如?機號、??密碼等敏感數據落庫需要加密。


項目啟動

我們創建項目要加入的依賴:

? ? ? ? 我們根據阿里巴巴java開發手冊來進行分層


? ? ? ? 我們項目應用到的層


? ? ? ? ?我們進行創建

2.功能模塊設計

通用處理

????????1. 錯誤碼

400 401 402... 客戶端異常

500 501 502... 服務端異常

????????為什么需要錯誤碼?

? ? ? ??1. 明確性:錯誤碼提供了?種明確的?式來表?錯誤的狀態。與模糊的異常消息相?,錯誤碼能夠精確地指出問題所在。

????????2. 易檢索:錯誤碼通常是數字,便于在?志、監控系統或錯誤跟蹤系統中檢索和過濾。

????????3. 客?端處理:客?端可以根據錯誤碼進?特定的錯誤處理,?不是依賴于通?的異常處理。

????????4. 維護性:集中管理錯誤碼使得它們更容易維護和更新。如果業務邏輯發?變化,只需要更新錯誤碼的定義,?不需要修改每個使?它們的地?。在接??檔中,錯誤碼也可以清晰地列出所有可能的錯誤情況,使開發者更容易理解和使?接?。

????????5. 調試和測試:錯誤碼可以?于?動化測試,確保特定的錯誤情況被正確處理。

????????6. 錯誤分類:錯誤碼可以幫助將錯誤分類為不同的級別或類型,如客?端錯誤、服務器錯誤、業務邏輯錯誤等。(也可能是分層分類)

? ? ? ? 我們用int code 來代表400,500,String msg 對應的描述; 500 -> 服務端XXX異常

? ? ? ? 注意:?/**?然后按?Enter?是多行文檔注釋的快捷鍵

? ? ? ? 錯誤碼類型定義:
package org.xiaobai.lotterysystem.common.errorcode;import lombok.Data;@Data
public class ErrorCode {//錯誤碼private final Integer code;//錯誤描述private final String msg;public ErrorCode(Integer code,String msg){this.code = code;this.msg = msg;}
}
????????定義全局錯誤碼:
package org.xiaobai.lotterysystem.common.errorcode;//全局錯誤碼
public interface GlobalErrorCodeConstants {ErrorCode SUCCESS = new ErrorCode(200,"成功! ");ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500,"系統異常");ErrorCode UNKNOWN = new ErrorCode(999,"未知錯誤");
}

定義業務錯誤碼--controller(后續完善)

定義業務錯誤碼--service(后續完善)

????????2. 自定義異常類

????????ControllerException:controller 層異常類

package org.xiaobai.lotterysystem.common.exception;import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xiaobai.lotterysystem.common.errorcode.ErrorCode;@Data
@EqualsAndHashCode(callSuper = true)
public class ControllerException extends RuntimeException{/*** @see org.xiaobai.lotterysystem.common.errorcode.ControllerErrorCodeConstants*/private Integer code;//異常信息private String message;//寫不帶參數的構造是為了序列化//序列化的時候必須有不帶參數的構造才會序列化成功//序列化(Serialization)是將數據結構或對象轉換為可存儲或傳輸的格式的過程,通常是將其轉換為字節流。它常用于將對象從內存中轉換為文件、數據庫、網絡傳輸等介質中,以便在不同的系統或程序間進行交換。public ControllerException(){}public ControllerException(Integer code,String message){this.code = code;this.message = message;}public ControllerException(ErrorCode errorCode){this.code = errorCode.getCode();this.message=errorCode.getMsg();}
}

????????ServiceException:service 層異常類

package org.xiaobai.lotterysystem.common.exception;import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xiaobai.lotterysystem.common.errorcode.ErrorCode;@Data//這個注解會生成自己的equals 和 hashcode,如果我們要使用父類的熟悉就要用下面的注解
@EqualsAndHashCode(callSuper = true)
public class ServiceException extends RuntimeException {/*** 異常碼** @see org.xiaobai.lotterysystem.common.errorcode.ServiceErrorCodeConstants*/private Integer code;//異常信息private String message;public ServiceException() {}public ServiceException(Integer code, String message) {this.code = code;this.message = message;}public ServiceException(ErrorCode errorCode) {this.code = errorCode.getCode();this.message = errorCode.getMsg();}}

? ? ? ? 注意:

????????我們無參構造方法的作用是為了序列化

????????序列化(Serialization)是將數據結構或對象轉換為可存儲或傳輸的格式的過程,通常是將其轉換為字節流。它常用于將對象從內存中轉換為文件、數據庫、網絡傳輸等介質中,以便在不同的系統或程序間進行交換。

? ? ? ? 關于@Data的使用

????????這個注解會生成自己的equals 和 hashcode,如果我們要使用父類的熟悉就要用
@EqualsAndHashCode(callSuper = true)

? ? ? ? 關于@See注解的使用

????????可以在注釋中實現鏈接跳轉.@See可以指向包,類,方法,屬性.用來鏈接代碼,方便我們追溯程序。提高可讀性.

????????3. CommonResult<T>

????????CommonResult<T> 作為控制器層?法的返回類型,封裝 一個統一的HTTP 接?調?的結果,包括成功數據、錯誤信息和狀態碼。它可以被 Spring Boot 框架等?動轉換為 JSON 或其他格式的響應體,發送給客?端.(使用泛型運行返回任意數據類型)

????????為什么要封裝?
? ? ? ? 如果不同接口返回的類型不一樣,對前端不友好,要進行判斷

? ? ? ? 其他的原因:

????????1. 統?的返回格式:確保客?端收到的響應具有?致的結構,?論處理的是哪個業務邏輯。

????????2. 錯誤碼和消息:提供錯誤碼(code)和錯誤消息(msg),幫助客?端快速識別和處理錯誤。

????????3. 泛型數據返回:使?泛型 <T> 允許返回任何類型的數據,增加了返回對象的靈活性。

????????4. 靜態?法:提供了 error() 和 success() 靜態?法,?便快速創建錯誤或成功的響應對象。

????????5. 錯誤碼常量集成:通過 ErrorCode 和 GlobalErrorCodeConstants 使?預定義的錯誤碼,保持錯誤碼的?致性和可維護性。

????????6. 序列化:實現了 Serializable 接?,使得 CommonResult<T> 對象可以被序列化為多種格式,如JSON或XML,?便?絡傳輸。

????????7. 業務邏輯解耦:將業務邏輯與API的響應格式分離,使得后端開發者可以專注于業務邏輯的實現,?不必關?如何構建HTTP響應。

????????8. 客戶端友好:客?端開發者可以通過統?的接?獲取數據和錯誤信息,?需針對每個API編寫特定的錯誤處理邏輯。

????????在實際應?中,CommonResult<T> 作為控制器層?法的返回類型,可以被 Spring boot 框架等?動轉換為 JSON 或其他格式的響應體,發送給客?端。這種?式提?了API的可?性和可維護性,同時也提升了客?端開發者的體驗。
????????具體代碼:

package org.xiaobai.lotterysystem.common.pojo;import org.springframework.util.Assert;
import org.xiaobai.lotterysystem.common.errorcode.ErrorCode;
import org.xiaobai.lotterysystem.common.errorcode.GlobalErrorCodeConstants;import java.io.Serializable;public class CommonResult <T> implements Serializable {//為了進行各種協議的序列化我們使用這個接口//表示當前調用成功或者失敗//返回的錯誤碼private Integer code;//正常返回的數據private T data;//錯誤碼描述private String msg;//成功public static <T> CommonResult<T> success(T data){CommonResult<T> rs = new CommonResult<>();rs.code = GlobalErrorCodeConstants.SUCCESS.getCode();rs.data = data;rs.msg = "";return rs;}//錯誤public static <T> CommonResult<T> error(Integer code,String msg){//判斷是不是傳錯了Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code),"code 不是錯誤的異常");CommonResult<T> rs = new CommonResult<>();rs.code = code;rs.msg = msg;return rs;}public static <T> CommonResult<T> error(ErrorCode errorCode){return error(errorCode.getCode(),errorCode.getMsg());}
}

????????4. jackson

????????序列化工具(想觀察對象,先轉化為string...)

? ? ? ? 1> 日志打印

? ? ? ? 2> redis key value? ?rabbitmq?

? ? ? ? 工具: fastjson jackson(使用這個,因為可視化比較好) protobuf(PB)速度很快,但是可視化很差

? ? ? ? ?我們先來寫個序列化和反序列化的例子

? ? ? ? 代碼詳細解釋

????????

  ObjectMapper objectMapper = new ObjectMapper();CommonResult<String> result = CommonResult.error(500, "系統錯誤");String str;// 序列化try {str = objectMapper.writeValueAsString(result);System.out.println(str);} catch (JsonProcessingException e) {throw new RuntimeException(e);}// 反序列化try {CommonResult<String> readResult = objectMapper.readValue(str,CommonResult.class);System.out.println(readResult.getCode() + readResult.getMsg());} catch (JsonProcessingException e) {throw new RuntimeException(e);}

? ? ? ? 反序列化list,map....需要多一步處理: 因為是泛型,所以為了避免擦除機制導致不曉得反序列化是什么類型,用javaType來解決

// List 序列化List<CommonResult<String>> commonResults = Arrays.asList(CommonResult.success("success1"),CommonResult.success("success2"));try {str = objectMapper.writeValueAsString(commonResults);System.out.println(str);} catch (JsonProcessingException e) {throw new RuntimeException(e);}// List 反序列化JavaType javaType = objectMapper.getTypeFactory().constructParametricType(List.class, CommonResult.class);try {commonResults = objectMapper.readValue(str, javaType);for (CommonResult<String> commonResult : commonResults) {System.out.println(commonResult.getData());}} catch (JsonProcessingException e) {throw new RuntimeException(e);}


但是此刻,我們發現每次序列化和反序列化都要捕獲異常,覺得麻煩了,我們就可以借鑒底層的實現:

把要拋出異常的語句寫成lambd表達式的形式tryparse對異常進行統一 的處理.

具體應用

package org.xiaobai.lotterysystem.common.utils;import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.json.JsonParseException;
import org.springframework.util.ReflectionUtils;import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.Callable;public class JacksonUtil {private JacksonUtil() {}/*** 單例化*/private final static ObjectMapper OBJECT_MAPPER;static {OBJECT_MAPPER = new ObjectMapper();}private static ObjectMapper getObjectMapper() {return OBJECT_MAPPER;}private static  <T> T tryParse(Callable<T> parser) {return tryParse(parser, JacksonException.class);}private static  <T> T tryParse(Callable<T> parser, Class<? extends Exception> check) {try {return parser.call();} catch (Exception var4) {if (check.isAssignableFrom(var4.getClass())) {throw new JsonParseException(var4);}throw new IllegalStateException(var4);}}/*** 序列化方法** @param object* @return*/public static String writeValueAsString(Object object) {return JacksonUtil.tryParse(() -> {return JacksonUtil.getObjectMapper().writeValueAsString(object);});}/*** 反序列化** @param content* @param valueType* @return* @param <T>*/public static <T> T readValue(String content, Class<T> valueType) {return JacksonUtil.tryParse(() -> {return JacksonUtil.getObjectMapper().readValue(content, valueType);});}/*** 反序列化 List** @param content* @param paramClasses* @return* @param <T>*/public static <T> T readListValue(String content, Class<?> paramClasses) {JavaType javaType = JacksonUtil.getObjectMapper().getTypeFactory().constructParametricType(List.class, paramClasses);return JacksonUtil.tryParse(() -> {return JacksonUtil.getObjectMapper().readValue(content, javaType);});}}

解釋: 把序列化和反序列化的操作用lambda進行打包作為tryParse()的參數傳過去,然后對異常進行統一的處理

?5.?志處理

? ? ? ? 我們使用SLF4 + logback來達到程序展現日志的目的

? ? ? ? 本地(dev): 輸出到控制臺

? ? ? ? 服務器(prod/test): 輸出到目標目錄

? ? ? ? spring里面內置了SLF4,我們可以在程序中直接調用它

????????需要配置的xml和properties

????????properties:

spring.application.name=lottery-system## logback xml ##
logging.config=classpath:logback-spring.xml
spring.profiles.active=dev
# 部署后需要變成
# spring.profiles.active=test## MySql ##
#驅動類名稱
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#鏈接數據庫url
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/lottery_system?characterEncoding=utf8&useSSL=false
#鏈接數據庫用戶名
spring.datasource.username=root
#鏈接數據庫密碼
spring.datasource.password=123456.## MyBatis ##
#Mapper.xml 文件路徑
#mybatis.mapper-locations=classpath:mapper/*Mapper.xml
#駝峰自動轉換 user_id->userId
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.type-handlers-package=com.example.lotterysystem.dao.handler## 短信 ##
sms.access-key-id=LTAI5tHze5qVnURvNaUvxQN7
sms.access-key-secret=yoY1dN16PWKxG3dYS72qcRCaXyyu0u
sms.sign-name=bitejiuyeke## redis  spring boot 3.x ##
spring.data.redis.host=localhost
spring.data.redis.port=8888
# 連接空閑超過N(s秒、ms毫秒)后關閉,0為禁?,這?配置值和tcp-keepalive值?致
spring.data.redis.timeout=60s
# 默認使? lettuce 連接池
# 允許最?連接數,默認8(負值表?沒有限制)
spring.data.redis.lettuce.pool.max-active=8
# 最?空閑連接數,默認8
spring.data.redis.lettuce.pool.max-idle=8
# 最?空閑連接數,默認0
spring.data.redis.lettuce.pool.min-idle=0
# 連接?完時,新的請求等待時間(s秒、ms毫秒),超過該時間拋出異常JedisConnectionException,(默認-1,負值表?沒有限制)
spring.data.redis.lettuce.pool.max-wait=5s## 文件上傳 ##
# 目標路徑
pic.local-path=D:/PIC
# spring boot3 升級配置名
spring.web.resources.static-locations=classpath:/static/,file:${pic.local-path}## mq ##
spring.rabbitmq.host=124.71.229.73
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
#消息確認機制,默認auto
spring.rabbitmq.listener.simple.acknowledge-mode=auto
#設置失敗重試 5次
spring.rabbitmq.listener.simple.retry.enabled=true
spring.rabbitmq.listener.simple.retry.max-attempts=5## 郵件 ##
spring.mail.host=smtp.qq.com
spring.mail.username=2689241679@qq.com
# 你的授權碼:郵箱設置-》第三?服務-》開啟IMAP/SMTP服務-》獲取授權碼
spring.mail.password=kdllbncanswudgch
spring.mail.default-encoding=UTF-8## 線程池 ##
async.executor.thread.core_pool_size=10
async.executor.thread.max_pool_size=20
async.executor.thread.queue_capacity=20
async.executor.thread.name.prefix=async-service-

? ? xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="60 seconds" debug="false"><springProfile name="dev"><!--輸出到控制臺--><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n%ex</pattern></encoder></appender><root level="info"><appender-ref ref="console" /></root></springProfile><springProfile name="prod,test"><!--ERROR級別的日志放在logErrorDir目錄下,INFO級別的日志放在logInfoDir目錄下--><property name="logback.logErrorDir" value="/root/lottery-system/logs/error"/><property name="logback.logInfoDir" value="/root/lottery-system/logs/info"/><property name="logback.appName" value="lotterySystem"/><contextName>${logback.appName}</contextName><!--ERROR級別的日志配置如下--><appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender"><!--日志名稱,如果沒有File 屬性,那么只會使用FileNamePattern的文件路徑規則如果同時有<File>和<FileNamePattern>,那么當天日志是<File>,明天會自動把今天的日志改名為今天的日期。即,<File> 的日志都是當天的。--><File>${logback.logErrorDir}/error.log</File><!-- 日志level過濾器,保證error.***.log中只記錄ERROR級別的日志--><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><!--滾動策略,按照時間滾動 TimeBasedRollingPolicy--><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--文件路徑,定義了日志的切分方式——把每一天的日志歸檔到一個文件中,以防止日志填滿整個磁盤空間--><FileNamePattern>${logback.logErrorDir}/error.%d{yyyy-MM-dd}.log</FileNamePattern><!--只保留最近14天的日志--><maxHistory>14</maxHistory><!--用來指定日志文件的上限大小,那么到了這個值,就會刪除舊的日志--><!--<totalSizeCap>1GB</totalSizeCap>--></rollingPolicy><!--日志輸出編碼格式化--><encoder><charset>UTF-8</charset><pattern>%d [%thread] %-5level %logger{36} %line - %msg%n%ex</pattern></encoder></appender><!--INFO級別的日志配置如下--><appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender"><!--日志名稱,如果沒有File 屬性,那么只會使用FileNamePattern的文件路徑規則如果同時有<File>和<FileNamePattern>,那么當天日志是<File>,明天會自動把今天的日志改名為今天的日期。即,<File> 的日志都是當天的。--><File>${logback.logInfoDir}/info.log</File><!--自定義過濾器,保證info.***.log中只打印INFO級別的日志, 填寫全限定路徑--><filter class="org.xiaobai.lotterysystem.common.filter"/><!--滾動策略,按照時間滾動 TimeBasedRollingPolicy--><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--文件路徑,定義了日志的切分方式——把每一天的日志歸檔到一個文件中,以防止日志填滿整個磁盤空間--><FileNamePattern>${logback.logInfoDir}/info.%d{yyyy-MM-dd}.log</FileNamePattern><!--只保留最近14天的日志--><maxHistory>14</maxHistory><!--用來指定日志文件的上限大小,那么到了這個值,就會刪除舊的日志--><totalSizeCap>1GB</totalSizeCap></rollingPolicy><!--日志輸出編碼格式化--><encoder><charset>UTF-8</charset><pattern>%d [%thread] %-5level %logger{36} %line - %msg%n%ex</pattern></encoder></appender><root level="info"><appender-ref ref="fileErrorLog" /><appender-ref ref="fileInfoLog"/></root></springProfile>
</configuration>

?????定義過濾器

????????本地不能體現,當項?部署到服務器上之后,根據配置?件得知,錯誤?志要放在error.log下,正常的?志打印放在 info.log中,要實現將 info.***.log中只打印INFO級別的?志,需要添加?下代碼:?

package org.xiaobai.lotterysystem.common.filter;import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;public class InfoLevelFilter extends Filter<ILoggingEvent> {@Overridepublic FilterReply decide(ILoggingEvent iLoggingEvent) {//把Info級別的日志過濾出來if (iLoggingEvent.getLevel().toInt() == Level.INFO.toInt()) {return FilterReply.ACCEPT;}return FilterReply.DENY;}
}

????????配置的對應關系

? ?用戶模塊

????????1. 注冊

????????1.1 敏感字段加密?

?????????般來說,??注冊時,需要輸?其賬?密碼及?機號,服務器應該將其保存起來,?便后續登錄驗證。但僅從道德的?度來說,后端不應該以明?形式存儲??密碼以及其他敏感信息。(因為如果后面因為bug把數據邂逅出去就不好了)

????????? 從運維層?看,任何操作系統漏洞、基礎?具漏洞的發?,都會導致密碼泄露

????????? 從開發層?看,任何代碼邏輯有漏洞、任何依賴庫的漏洞都可能導致密碼泄露

????????? 從管理層?看,任何?個有權限讀取數據庫的?,都能看到所有??的密碼(怕直接泄露表數據)

? ? ? ? 我們可以把數據進行加密操作

????????密碼如何加密?

????????? 對稱加密?

????????? ?如3DES、AES等算法,使?這種?式加密是可以通過解密來還原出原始密碼的,當然前提

????????條件是需要獲取到密鑰。密鑰很可能也會泄露,當然可以將?般數據和密鑰分開存儲、分開

管理,但要完全保護好密鑰也是?件?常復雜的事情,所以這種?式并不是很好的?式。

????????? 哈希?

????????? 加密不可逆

????????? 彩虹表攻擊

????????? 加鹽哈希?加鹽哈希是?前業界最常?的做法。

????????? ??注冊時,給他隨機?成?段字符串,這段字符串就是鹽(Salt)(Hash結果固定)

????????? 把??注冊輸?的密碼和鹽拼接在?起,叫做加鹽密碼

????????? 對加鹽密碼進?哈希,并把結果和鹽都儲存起來

?????????機號如何加密?

????????除了密碼以外,?機號等信息也是重要的隱私數據。但?機號與密碼不同:對于后端來說,永遠不知道密碼的明?也不會對業務邏輯造成影響;?后端可能需要明?的?機號,在?些情況下給??發送短信。

????????因此對于?機號這種信息,只能?相對安全的做法,即先對?機號進?對稱加密,再將加密結果儲存在數據庫?;使?時再?密鑰解開。

????????加密?具

????????國產 Java?具類庫 Hutool,對?件、流、加密解密、轉碼、正則、線程、XML 等 JDK ?法進?了封裝,開箱即?!

????????引? jar 包:

Maven 倉庫地址:https://mvnrepository.com/artifact/cn.hutool HuTool

官?地址:https://hutool.cn/
?

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>

? ? ? ? 我們對手機號(對稱加密),因為它需要解密,我們給用戶發送獲獎信息)和密碼(hash加密,不需要解密,直接進行比對即可)進行加鹽hash操作.?

? ? ? ? 哈希加密 sha256 -> 密碼

? ? ? ? 對稱加密 aes -> 手機號

? ? ? ? 代碼實現

package org.xiaobai.lotterysystem;import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.symmetric.AES;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.DigestUtils;import java.nio.charset.StandardCharsets;@SpringBootTest
public class EncryptTest {//密碼采用 hash加密 sha256@Testvoid sha256Test() {String encrypt = DigestUtil.sha256Hex("123456789");System.out.println("經過sha256的結果: "+ encrypt);//15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225}//手機號 對稱加密 使用 aes@Testvoid aesTest(){//密鑰:長度的設置要為16(128位) 24(192) 32(256)byte[] KEY = "123456789abcdefg".getBytes(StandardCharsets.UTF_8);//加密AES aes = SecureUtil.aes(KEY);String encrypt = aes.encryptHex("123456789");System.out.println("aes的加密結果為: "+encrypt);//解密System.out.println("aes的解密結果為: "+aes.decryptStr(encrypt));}
}

????????1.2 用戶注冊

? ? ? ? 注冊場景:?

? ? ? ? 1> 登錄頁面點擊注冊,就有注冊頁面

? ? ? ? 2> 管理員在后臺進行注冊用戶

????????時序圖

????????

約定前后端交互接口

controller: 請求的入口

service: 處理業務邏輯

dao: 與數據層(mysql)進行交互

Controller 層接口設計

? ? ? ? UserController

package org.xiaobai.lotterysystem.controller;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.xiaobai.lotterysystem.common.errorcode.ControllerErrorCodeConstants;
import org.xiaobai.lotterysystem.common.exception.ControllerException;
import org.xiaobai.lotterysystem.common.pojo.CommonResult;
import org.xiaobai.lotterysystem.common.utils.JacksonUtil;
import org.xiaobai.lotterysystem.controller.param.UserRegisterParam;
import org.xiaobai.lotterysystem.controller.result.UserRegisterResult;
import org.xiaobai.lotterysystem.service.UserService;
import org.xiaobai.lotterysystem.service.dto.UserRegisterDTO;@RestController
public class UserController {private static final Logger logger = LoggerFactory.getLogger(UserController.class);@Autowiredprivate UserService userService;//注冊@RequestMapping("/register")public CommonResult<UserRegisterResult> userRegister(//@Validated表示要對傳過來的參數進行驗證@Validated @RequestBody UserRegisterParam param){//前端傳過來的是一個進行序列化的json字符串,我們需要設置接受的格式是json//打印日志,打印請求參數是否正確logger.info("userController UserRegisterParam:{}", JacksonUtil.writeValueAsString(param));//把參數進行序列化//調用service進行訪問UserRegisterDTO userRegisterDTO = userService.register(param);return CommonResult.success(convertToUserRegisterResult(userRegisterDTO));//把它轉換位CommonResult<UserRegisterResult>統一信息返回類型,進行http請求的返回}private UserRegisterResult convertToUserRegisterResult(UserRegisterDTO userRegisterDTO) {UserRegisterResult rs = new UserRegisterResult();if(null == userRegisterDTO){throw new ControllerException(ControllerErrorCodeConstants.REGISTER_ERROR);}//沒有異常就進行類型的轉換rs.setUserId(userRegisterDTO.getUserId());return rs;}
}

? ? ? ? 參數類

package org.xiaobai.lotterysystem.controller.param;import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.springframework.validation.annotation.Validated;@Data
public class UserRegisterParam {//@Validated//設置必填字段//姓名@NotBlank(message = "姓名不能為空!")private String name;//郵箱@NotBlank(message = "郵箱不能為空!")private String mail;//電話@NotBlank(message = "電話不能為空!")private String phoneNumber;//密碼private String password;//身份信息@NotBlank(message = "身份信息不能為空!")private String identity;
}

? ? ? ? 結果類:

package org.xiaobai.lotterysystem.controller.result;import lombok.Data;import java.io.Serializable;@Data
public class UserRegisterResult implements Serializable {private Long userId;
}

? ? ? ? 其中注意,我們要對注冊的信息填寫進行必填和非必填的設置,此時可以用到

????????Validation

????????對于 controller 接??參字段的驗證,可以使? Spring Boot 中集成的 Validation 來完成。例如可以看到我們在接??參上加?了 @Validated 注解,并且 param 對象中的每個成員都使?@NotBlank 注解來檢查參數不能為空。使?需引?依賴

<!-- spring-boot 2.3及以上的版本只需要引?下?的依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
????????Service 層接口設計

? ? ? ? 下面我們這個使用了接口分離的設計

????????為什么進?接?分離設計?接?與實現的分離是 Java 編程中推崇的?種設計哲學,它有助于創建更加靈活、可維護和可擴展的軟件系統。

????????1. 抽象與具體實現分離:接?定義了?組操作的契約(接口里面定義了一些協議),?實現則提供了這些操作的具體?為。這種分離允許改變具體實現?不影響使?接?的客?端代碼。(后面修改的時候不需要改接口,直接改實現類即可)

????????2. ?持多態性:接?允許通過共同的接?來引?不同的實現,這是多態性的基礎,使得代碼更加靈活和通?。

????????3. 提?代碼的可讀性和可理解性:接?提供了清晰的 API 視圖,使得其他開發者能夠更容易地理解和使?這些 API。(直接看接口里面定義的方法名稱就可以大致曉得是干啥,沒有方法實現)

????????4. 安全性:接?可以隱藏實現細節,只暴露必要的操作,這有助于保護系統的內部狀態和實現不被外部直接訪問。

????????5. 遵循開閉原則:軟件實體應當對擴展開放,對修改封閉。接?與實現的分離使得在不修改客?端代碼的情況下擴展系統的功能。

????????6. 促進?向對象的設計:接?與實現的分離?勵開發者進??向對象的設計,考慮如何將系統分解為可重?和可組合的組件。

? ? ? ? 我們在判斷是否郵箱已經被注冊過需要調用數據庫里面的數據,要使用mybatis,此時我們要引入依賴(程序與數據庫相互交互的框架)

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>

? ? ? ? userServiceImpl: 對注冊的信息進行校驗操作

package org.xiaobai.lotterysystem.service.Impl;import cn.hutool.crypto.digest.DigestUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.xiaobai.lotterysystem.common.errorcode.ServiceErrorCodeConstants;
import org.xiaobai.lotterysystem.common.exception.ServiceException;
import org.xiaobai.lotterysystem.common.utils.RegexUtil;
import org.xiaobai.lotterysystem.controller.param.UserRegisterParam;
import org.xiaobai.lotterysystem.dao.dataobject.Encrypt;
import org.xiaobai.lotterysystem.dao.dataobject.UserDO;
import org.xiaobai.lotterysystem.dao.mapper.UserMapper;
import org.xiaobai.lotterysystem.service.UserService;
import org.xiaobai.lotterysystem.service.dto.UserRegisterDTO;
import org.xiaobai.lotterysystem.service.enums.UserIdentityEnum;@Service
//接口的實現類
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserRegisterDTO register(UserRegisterParam param) {//校驗注冊信息checkRegisterInfo(param);//加密私密數據(構造dao層數據)UserDO userDo = new UserDO();userDo.setUserName(param.getName());userDo.setEmail(param.getMail());userDo.setPhoneNumber( new Encrypt(param.getPhoneNumber()));userDo.setIdentity(param.getIdentity());if(StringUtils.hasText(param.getPassword())){//判斷是否有密碼//有密碼就進行加密userDo.setPassword(DigestUtil.sha256Hex(param.getPassword()));}//保存數據//我們把DO傳進去userMapper.insert(userDo);//構造返回UserRegisterDTO userRegisterDTO = new UserRegisterDTO();userRegisterDTO.setUserId(userDo.getId());//獲取當前的idreturn userRegisterDTO;}private void checkRegisterInfo(UserRegisterParam param) {//為了避免空指針異常if(null == param){throw new ServiceException(ServiceErrorCodeConstants.REGISTER_INFO_IS_EMPTY);}//校驗郵箱格式 xxx@xxx.xxxif(!RegexUtil.checkMail(param.getMail())){throw new ServiceException(ServiceErrorCodeConstants.MAIL_ERROR);}//校驗手機號格式if(!RegexUtil.checkMobile(param.getPhoneNumber())){throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_ERROR);}//校驗身份信息if(null == UserIdentityEnum.forName(param.getIdentity())){throw new ServiceException(ServiceErrorCodeConstants.IDENTITY_ERROR);}//校驗管理員密碼必填//判斷是不是管理員身份if(param.getIdentity().equalsIgnoreCase(UserIdentityEnum.ADMIN.name())&& !StringUtils.hasLength(param.getPassword())){//是管理員,但是沒有寫密碼throw new ServiceException(ServiceErrorCodeConstants.PASSWORD_IS_EMPTY);}//密碼長度必須至少6位if(StringUtils.hasText(param.getPassword())&& !RegexUtil.checkPassword(param.getPassword())){//是管理員,但是長度不符合throw new ServiceException(ServiceErrorCodeConstants.PASSWORD_ERROR);}//校驗郵箱是否被使用if(checkMailUsed(param.getMail())){throw new ServiceException(ServiceErrorCodeConstants.MAIL_USED);}//校驗手機號是否被使用if(checkPhoneNumberUsed(param.getPhoneNumber())){throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_USED);}}//校驗手機號是否被使用private boolean checkPhoneNumberUsed(String phoneNumber) {//對于手機號而言,它是用對稱加密,所以我們需要進行解密才能獲得手機號int count = userMapper.countByPhone(new Encrypt(phoneNumber));return count > 0;}//校驗郵箱是否被使用private boolean checkMailUsed(String mail) {//去查數據庫表里面,有沒有這個數據//判斷郵箱綁定人數>0,就說明郵箱被使用過了int count = userMapper.coutByMail(mail);return count > 0;}
}

? ? ? ? enums: 枚舉我們的用戶類型

package org.xiaobai.lotterysystem.service.enums;import lombok.AllArgsConstructor;
import lombok.Getter;@Getter
@AllArgsConstructor
public enum UserIdentityEnum{ADMIN("管理員"),NORMAL("普通用戶");//private final String message;public static UserIdentityEnum forName(String name){for(UserIdentityEnum userIdentityEnum : UserIdentityEnum.values()){if(userIdentityEnum.name().equalsIgnoreCase(name)){return userIdentityEnum;}}return null;}}

????????UserRegisterDTO

package org.xiaobai.lotterysystem.service.dto;import lombok.Data;@Data
public class UserRegisterDTO {//DTO是阿里巴巴的規范,是數據傳輸對象,是service或者manager向外傳輸的對象//用戶IDprivate Long userId;
}

??命名為DTO的原因

其中校驗??信息,例如郵箱、電話、密碼格式的內容,我們封裝成了?個 util 來完成: ????????RegexUtil: 里面都是用正則表達式來規定格式

package org.xiaobai.lotterysystem.common.utils;import org.springframework.util.StringUtils;import java.util.regex.Pattern;/****/
public class RegexUtil {/*** 郵箱:xxx@xx.xxx(形如:abc@qq.com)** @param content* @return*/public static boolean checkMail(String content) {if (!StringUtils.hasText(content)) {//判斷是否郵箱內容為空return false;}/*** ^ 表示匹配字符串的開始。* [a-z0-9]+ 表示匹配一個或多個小寫字母或數字。* ([._\\-]*[a-z0-9])* 表示匹配零次或多次下述模式:一個點、下劃線、反斜杠或短橫線,后面跟著一個或多個小寫字母或數字。這部分是可選的,并且可以重復出現。* @ 字符字面量,表示電子郵件地址中必須包含的"@"符號。* ([a-z0-9]+[-a-z0-9]*[a-z0-9]+.) 表示匹配一個或多個小寫字母或數字,后面可以跟著零個或多個短橫線或小寫字母和數字,然后是一個小寫字母或數字,最后是一個點。這是匹配域名的一部分。* {1,63} 表示前面的模式重復1到63次,這是對頂級域名長度的限制。* [a-z0-9]+ 表示匹配一個或多個小寫字母或數字,這是頂級域名的開始部分。* $ 表示匹配字符串的結束。*/String regex = "^[a-z0-9]+([._\\\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$";return Pattern.matches(regex, content);}/*** 手機號碼以1開頭的11位數字** @param content* @return*/public static boolean checkMobile(String content) {if (!StringUtils.hasText(content)) {return false;}/*** ^ 表示匹配字符串的開始。* 1 表示手機號碼以數字1開頭。* [3|4|5|6|7|8|9] 表示接下來的數字是3到9之間的任意一個數字。這是中國大陸手機號碼的第二位數字,通常用來區分不同的運營商。* [0-9]{9} 表示后面跟著9個0到9之間的任意數字,這代表手機號碼的剩余部分。* $ 表示匹配字符串的結束。*/String regex = "^1[3|4|5|6|7|8|9][0-9]{9}$";return Pattern.matches(regex, content);}/*** 密碼強度正則,6到12位** @param content* @return*/public static boolean checkPassword(String content){if (!StringUtils.hasText(content)) {return false;}/*** ^ 表示匹配字符串的開始。* [0-9A-Za-z] 表示匹配的字符可以是:* 0-9:任意一個數字(0到9)。* A-Z:任意一個大寫字母(從A到Z)。* a-z:任意一個小寫字母(從a到z)。* {6,12} 表示前面的字符集合(數字、大寫字母和小寫字母)可以重復出現6到12次。* $ 表示匹配字符串的結束。*/String regex= "^[0-9A-Za-z]{6,12}$";return Pattern.matches(regex, content);}
}
????????Dao 層接口設計

? ? ? ? 我們用MyBatis 來完成程序和數據庫交互的框架

????????Encrypt

package org.xiaobai.lotterysystem.dao.dataobject;import lombok.Data;@Data
public class Encrypt {private String value;public Encrypt(){}//進行序列化的時候必須有無參的構造public Encrypt(String value){this.value = value;}
}

??????????EncryptTypeHandler: 處理Encryprt對象

package org.xiaobai.lotterysystem.dao.handler;import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.springframework.util.StringUtils;
import org.xiaobai.lotterysystem.dao.dataobject.Encrypt;import java.nio.charset.StandardCharsets;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@MappedTypes(Encrypt.class)//處理Encrypt為jdbc的哪個對象,里面裝的是被處理的類型
@MappedJdbcTypes(JdbcType.VARCHAR)//里面是轉換后的jdbc類型
public class EncryptTypeHandler extends BaseTypeHandler<Encrypt> {//密鑰private final byte[] KEY = "123456789abcdefg".getBytes(StandardCharsets.UTF_8);@Override//設置參數,ps: SQL 預編譯對象 i:需要賦值的索引位置,parameter:原本位置i需要賦的值,jdbcType:jdbc的類型public void setNonNullParameter(PreparedStatement ps, int i, Encrypt parameter, JdbcType jdbcType) throws SQLException {if(parameter == null || parameter.getValue() == null){ps.setString(i,null);}System.out.println("加密的內容: "+ parameter.getValue());//加密AES aes = SecureUtil.aes(KEY);String str = aes.encryptHex(parameter.getValue());//進行加密//重新賦值我們要修改的第i位ps.setString(i,str);}@Override//獲取值: rs: 結果集, columnName 索引名 直接可以從結果集對應的索引名稱獲取道數據庫里面的拿到的加密數據,然后我們再進行解密就可以了public Encrypt getNullableResult(ResultSet rs, String columnName) throws SQLException {System.out.println("獲取值得到的加密的內容: "+rs.getString(columnName));return decrypt( rs.getString(columnName));//解密操作}@Override//獲取值 rs 結果集, columIndex: 索引public Encrypt getNullableResult(ResultSet rs, int columnIndex) throws SQLException {System.out.println("獲取值得到的加密的內容: "+rs.getString(columnIndex));return decrypt(rs.getString(columnIndex));}@Override//獲取值 cs: 結果集 columnIndex 索引public Encrypt getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {System.out.println("獲取值得到的加密的內容: "+cs.getString(columnIndex));return decrypt(cs.getString(columnIndex));}//存儲的時候希望返回一些值,返回存儲過程中輸出的數據//結果集中獲取某一列的數據,并將其解密后返回。//解密方法private Encrypt decrypt(String str){if(!StringUtils.hasText(str)){return null;}return new Encrypt(SecureUtil.aes(KEY).decryptStr(str));}
}

UserMapper: 和數據庫進行交互

package org.xiaobai.lotterysystem.dao.mapper;import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.xiaobai.lotterysystem.dao.dataobject.Encrypt;@Mapper
public interface UserMapper {//查詢郵箱綁定人數@Select("select count(*) from user where email = #{email}")int coutByMail(@Param("email")String email);@Select("select count(*) from user where phone_number = #{phoneNumber}")int countByPhone(@Param("phoneNumber") Encrypt phoneNumber);
}

?注意:

?TypeHandler

@MappedTypes:表?該處理器處理的 java 類型是什么。

@MappedJdbcTypes:表?處理器處理的 Jdbc 類型。

對于上述代碼還有?個疑惑:?機號設置的類型為 Encrypt,Encrypt 是什么?

????????回歸最初的問題,我們對?機號進?存儲時,要先將?機號加密,如果要拿出使?時,還要進??次解密操作。為了不讓每次?動去加密解密,決定使? Mybatis 的 TypeHandler 來解決。 TypeHandler : 簡單理解就是當處理某些特定字段時,我們可以實現?些?法,讓 Mybatis 遇到這些特定字段可以?動運?處理。

總體的調用流程: 我們先把我們的手機號包成Encrypty,然后我們使用Spring里面自帶的TypeHandler,自動對我們的電話號碼進行加密和解密操作

????????

公共key不允許獲得異常: 在propteries里面設置一下:allowPublickRetrieval=true

完美解決:MySQL8報錯:Public Key Retrieval is not allowed_mysql8 public key retrieval is not allowed-CSDN博客

注意:

application.properties 中指定 Typehandler 的包路徑,#type-handlers處理路徑,替換成??的的

mybatis.type-handlers-package=com.example.dao.handler

這里補充一個插件,可以不手動打開數據庫,直接對我們的數據進行管理

Database Navigator 插件
????????IDEA 數據庫插件Database Navigator 插件是 IntelliJ IDEA 集成開發環境中的?個重要組件,它為開發者提供了?種?便快捷的數據庫管理和開發?具。通過 Database Navigator 插件,開發者可以連接到各種類型的數據庫,執? SQL 查詢和更新數據,以及通過可視化的?式設計和維護數據庫表結構。

? ? ? ? 下面進行它的插件安裝以及使用

install 成功后,點擊ok,然后進?restart.
3. 找到并進?DB Browser(DB Browser 可能會在項?最左欄,可根據習慣設置)

或者在這?找

選擇 MySQL

然后寫上自己要用的數據庫的名稱

注意,我們在更新表之后想觀察更新后的數據,需要斷開連接重啟,才能進行更新

控制層通用異常處理 @RestControllerAdvice+@ExceptionHandler(攔截器的使用)

? ? ? ? 我們并沒有對controller層進行try-catch,如果我們后續在service或者其他層出現了異常,如果沒有抓住的話,就會把錯誤暴露給前端,對用戶的體驗很不好,但是我們每個請求都要寫try-catch是個冗余工作,可以對異常進行統一的處理,此時我們可以使用攔截器

????????spring boot中使? @RestControllerAdvice 注解,完成優雅的全局異常處理類,可以針對所有異常類型先進?通?處理后,再對特定異常類型進?不同的處理操作。它可以捕獲全局的mvc里面的異常.@ExceptionHandler它可以指定某個異常繼續拋出(未知的和已知的都可以進行捕獲)

package org.xiaobai.lotterysystem.controller.handler;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.xiaobai.lotterysystem.common.errorcode.GlobalErrorCodeConstants;
import org.xiaobai.lotterysystem.common.exception.ControllerException;
import org.xiaobai.lotterysystem.common.exception.ServiceException;
import org.xiaobai.lotterysystem.common.pojo.CommonResult;@RestControllerAdvice//可以捕獲全局拋出來的異常
public class GlobalExceptionHandler {private final static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);@ExceptionHandler(value = ControllerException.class)public CommonResult<?> controllerException(ControllerException e){//打錯誤日志logger.error("controllerException",e);//構造錯誤結果return CommonResult.error(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),e.getMessage());}//未知的異常@ExceptionHandler(value = Exception.class)public CommonResult<?> Exception(Exception e){//打錯誤日志logger.error("服務異常",e);//構造錯誤結果return CommonResult.error(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),e.getMessage());}}

使用后我們發現,我們少些個參數,是返回的我們自定義的統一異常信息

郵箱被使用

? ? ? ? 前端頁面展示

成功

失敗

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/896926.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/896926.shtml
英文地址,請注明出處:http://en.pswp.cn/news/896926.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

vim 調整字體

vim: 在vim 面板單擊右鍵&#xff0c;選擇references: terminal :也是單擊右鍵,選擇references:

UniApp 使用 u-loadmore 完整步驟

文章目錄 一、前期準備1. 安裝 uView - UI 二、使用 u-loadmore組件1. 創建頁面2. 編寫頁面代碼模板部分&#xff08;loadmore-demo.vue&#xff09;樣式部分腳本部分 三、要點補充1. u-loadmore 狀態說明2. 數據請求優化3. 性能優化4. 兼容性問題 在 UniApp 開發中&#xff0c…

Libgdx游戲開發系列教程(3)——通過柏林噪音算法地圖隨機地形

在B站刷到了隨機地圖生成的視頻,隨手學習下并做下記錄 注: 本篇使用javafx應用作演示,算是了解這個算法的使用,后續會再出篇libgdx生成地圖的示例 說明 拋開算法實現,首先認知柏林噪音算法 一般我們想要隨機數,會指定個范圍,如0.0-1.0之間任意小數,而柏林算法的結果范圍就是[…

LeetCode熱題100JS(20/100)第四天|?41. 缺失的第一個正數?|?73. 矩陣置零?|?54. 螺旋矩陣?|?48. 旋轉圖像?

41. 缺失的第一個正數 題目鏈接&#xff1a;41. 缺失的第一個正數 難度&#xff1a;困難 刷題狀態&#xff1a;1刷 新知識&#xff1a; 解題過程 思考 示例 1&#xff1a; 輸入&#xff1a;nums [1,2,0] 輸出&#xff1a;3 解釋&#xff1a;范圍 [1,2] 中的數字都在數組中…

e2studio開發RA2E1(17)---- ADC掃描多通道采樣

e2studio開發RA2E1.17-- ADC掃描多通道采樣 概述視頻教學樣品申請硬件準備參考程序源碼下載ADC屬性配置回調函數主程序演示結果 概述 在嵌入式系統中&#xff0c;ADC&#xff08;模數轉換器&#xff09;是一個非常重要的組件&#xff0c;它將模擬信號轉換為數字信號。為了提高…

FPGA標準庫-Open Logic

在現代技術發展的浪潮中&#xff0c;開源項目已經成為了推動技術創新和發展的核心力量。無論是人工智能、區塊鏈、云計算&#xff0c;還是傳統的嵌入式開發、操作系統&#xff0c;開源項目都在其中扮演著至關重要的角色。它們不僅促進了技術的快速迭代&#xff0c;也為全球開發…

FineReport 操作注意

1.父單元格重復的時候&#xff0c;如何取消合并 效果如下&#xff1a; 只需要在單元格中&#xff0c;將數據設置為【列表】即可。 2.待定

開源之夏經驗分享|Koupleless 社區黃興抗:在開源中培養工程思維

開源之夏經驗分享&#xff5c;Koupleless 社區黃興抗&#xff1a;在開源中培養工程思維 文|黃興抗 電子信息工程專業 Koupleless 社區貢獻者 就讀于南昌師范學院&#xff0c;電子信息工程專業的大三學生。 本文 2634 字&#xff0c;預計閱讀 7? 分鐘? 今天 SOFAStack 邀…

Ollama存在安全風險的情況通報及解決方案

據清華大學網絡空間測繪聯合研究中心分析&#xff0c;開源跨平臺大模型工具Ollama默認配置存在未授權訪問與模型竊取等安全隱患。鑒于目前DeepSeek等大模型的研究部署和應用非常廣泛&#xff0c;多數用戶使用Ollama私有化部署且未修改默認配置&#xff0c;存在數據泄露、算力盜…

線代[9]|線性代數主要內容及其發展簡史(任廣千《線性代數的幾何意義》的附錄1)

文章目錄 向量行列式矩陣線性方程組二次型 向量 向量又稱為矢量&#xff0c;最初應用與物理學。很多物理量如力、速度、位移以及電場強度、磁感應強度等等都是向量。大約公元前350年前&#xff0c;古希臘著名學者亞里士多德就知道了力可以表示成向量&#xff0c;兩個力的組合作…

H20半精度推理報錯:Floating point exception (core dumped)

Nvidia H20 顯卡在執行bf16&#xff0c;f16推理時程序異常中斷 時間是 2025年3月4日 課題組新到的8卡H20服務器在使用過程中&#xff0c;torch加載模型進行bf16的推理時&#xff0c;出現Floating point exception (core dumped)錯誤 當時一頭霧水&#xff0c;后來苦苦尋找&…

服務是否設置為開機自啟動

在 Linux 系統中&#xff0c;可以通過以下幾種方法檢查服務是否設置為開機自啟動&#xff1a; 方法 1&#xff1a;使用 systemctl 命令&#xff08;適用于 systemd 系統&#xff09; systemctl 是 systemd 系統的命令行工具&#xff0c;用于管理系統服務。以下是具體步驟&…

QT——基于 QListWidget 和 QStackedWidget 的頁面切換

Qt 練習題&#xff1a;基于 QListWidget 和 QStackedWidget 的頁面切換 Qt 練習題&#xff1a;基于 QListWidget 和 QStackedWidget 的頁面切換 題目描述&#xff1a; 請使用 Qt 設計一個窗口&#xff0c;其中包含一個 QListWidget 和一個 QStackedWidget。要求實現以下功能&a…

DeepSeek 助力 Vue3 開發:打造絲滑的表格(Table)示例2: 分頁和排序

前言:哈嘍,大家好,今天給大家分享一篇文章!并提供具體代碼幫助大家深入理解,徹底掌握!創作不易,如果能幫助到大家或者給大家一些靈感和啟發,歡迎收藏+關注哦 ?? 目錄 DeepSeek 助力 Vue3 開發:打造絲滑的表格(Table)示例2: 分頁和排序??前言??頁面效果??指令…

C語言文件操作學習筆記:從基礎到實踐

在C語言的知識體系中&#xff0c;文件操作是極為關鍵的一環&#xff0c;它賦予了程序存儲和讀取外部數據的能力&#xff0c;對于開發各類實用程序至關重要。近期&#xff0c;借助課程的學習&#xff0c;我對C語言文件操作進行了系統且深入的學習&#xff0c;下面將我的學習心得…

VLM-E2E:通過多模態駕駛員注意融合增強端到端自動駕駛

25年2月來自香港科大廣州分校、理想汽車和廈門大學的論文“VLM-E2E: Enhancing End-to-End Autonomous Driving with Multimodal Driver Attention Fusion”。 人類駕駛員能夠利用豐富的注意語義&#xff0c;熟練地應對復雜場景&#xff0c;但當前的自動駕駛系統難以復制這種能…

第十天-字符串:編程世界的文本基石

在編程的廣闊領域中&#xff0c;字符串是極為重要的數據類型&#xff0c;它就像一座橋梁&#xff0c;連接著人類的自然語言和計算機能夠理解與處理的數字信息。下面&#xff0c;讓我們深入探索字符串的世界。 一、字符串簡介 字符串是由零個或多個字符組成的有序序列&#xff…

《基于HarmonyOS NEXT API 12+,搭建新聞創作智能寫作引擎》

在信息爆炸的時代&#xff0c;新聞行業對于內容生產的效率和質量有著極高的要求。AI技術的發展為新聞創作帶來了新的變革契機&#xff0c;借助AI智能寫作助手&#xff0c;新聞工作者可以快速生成新聞稿件的初稿&#xff0c;大大提高創作效率。本文將基于HarmonyOS NEXT API 12及…

基于STM32的環境監測系統(自制藍牙APP)

目錄 項目概述 實物圖 演示視頻 概述 硬件模塊 原理圖以及PCB 0.96寸OLED屏幕&#xff08;SSD1306&#xff09; CubeMX配置 初始化代碼 MQ-2煙霧傳感器 CubeMX配置 初始化代碼 DHT11溫濕度模塊 驅動代碼 HC-05藍牙模塊 CubeMX配置 ?編輯 空閑中斷回調函數 有…

linux離線安裝ollama并部署deepseek-r1模型 指南

這篇文章主要分為兩部分&#xff1a; (1)離線環境下如何部署Ollama&#xff1b; (2)在離線環境下如何配置大模型&#xff0c;其中這一步又分為&#xff1a; ?1)部署完整的deepseek大模型&#xff0c;如&#xff1a;deepseek-r1:32B; ?2)部署蒸餾版模型&#xff0c;如&#xf…