[TOC]css
服務器如今過時了,項目沒法訪問,敬請諒解html
源碼地址:java
小程序源碼 https://github.com/w77996/mini-straw
後臺源碼 https://github.com/w77996/hi-straw
複製代碼
體驗一下mysql
以前乘着換工做的間隙擼的,一方面練習一下小程序,學過以後沒怎麼應用,一方面寫點筆記啥的,項目斷斷續續作了兩個月,只是一個簡單的圖片上傳工具,以爲不錯的話記得給個starlinux
項目目錄結構nginx
mini-straw
├── component -- 組件
| ├── file -- 文件組件
| ├── image-button -- 圖片按鈕組件
| ├── search -- 查找頁面組件
| ├── tag -- 標籤組件
├── images -- 圖片目錄
| ├── icon -- icon圖片
| ├── tab -- tab圖片
├── model -- 封裝的model
├── pages -- 頁面
| ├── about -- 關於頁
| ├── auth -- 受權頁
| ├── file -- 文件頁
| ├── index -- 首頁
| ├── launch -- 啓動頁面
| ├── my -- 我的中心
└── utils -- 工具
複製代碼
技術棧:spring boot + druid + mybatis + jwtgit
項目目錄結構github
hi-straw
├── common -- 公共模塊
├── config -- 配置模塊
├── controller -- controller接口
├── core -- 核心業務模塊
| ├── annontaion -- 註解
| ├── aop -- aop實現
| ├── constant -- 常量
| ├── filter -- 攔截器
| ├── jwt -- jwt相關
| └── result -- 結果返回
├── entity -- 實體類
| ├── dto -- 數據傳輸
| └── vo -- 頁面傳輸
├── exception -- 全局異常
├── mapper -- dao層
├── service -- service層
└── util -- 工具類
複製代碼
[TOC]web
mini-strawspring
mini-straw
├── component -- 組件
| ├── file -- 文件組件
| ├── image-button -- 圖片按鈕組件
| ├── search -- 查找頁面組件
| ├── tag -- 標籤組件
├── images -- 圖片目錄
| ├── icon -- icon圖片
| ├── tab -- tab圖片
├── model -- 封裝的model
├── pages -- 頁面
| ├── about -- 關於頁
| ├── auth -- 受權頁
| ├── file -- 文件頁
| ├── index -- 首頁
| ├── launch -- 啓動頁面
| ├── my -- 我的中心
└── utils -- 工具
複製代碼
使用wx.getNetworkType({})
可獲取當前網絡狀態,networkType
值wifi/2g/3g/4g/unknown(Android下不常見的網絡類型)/none(無網絡)
//判斷網絡狀態
wx.getNetworkType({
success: res => {
if (res.networkType == "none") {
wx.showToast({
title: '嗷~~網絡不可用',
icon: 'none',
duration: 2000
})
return;
}
},
})
複製代碼
使用wx.getSetting({})
獲取受權狀態,在得到data
後取data.authSetting['scope.userInfo']
判斷受權狀態
// 獲取受權的狀態
wx.getSetting({
success: data => {
if (data.authSetting['scope.userInfo']) {
//已受權,執行登錄
wx.getUserInfo({
success: data => {
console.log("userInfo {}", data)
let userInfo = data.userInfo;
wx.setStorageSync('userInfo', userInfo);
//執行登錄操做
this._userLoginGetCode(userInfo);
}
});
wx.setStorageSync('authorized', true);
} else {
console.log("未受權")
//跳轉至受權頁
let timer = setTimeout(() => {
wx.redirectTo({
url: '/pages/auth/auth'
})
}, 2000)
}
}
});
複製代碼
若受權,則調用wx.getUserInfo({})
獲取微信用戶信息,信息獲取完成後調用wx.login({})
獲取小程序的code
,經過code
向後臺獲取用戶openId及token。
//後臺獲取code
_userLoginGetCode(userInfo) {
console.log("發起_userLoginGetCode請求");
wx.login({
success(res) {
console.log("wx.login {}", res);
if (res.code) {
// 發起網絡請求
const code = res.code;
userInfo.code = code;
userModel.getTokenByCode(userInfo).then((res) => {
console.log("userModel getUserInfo {}", res);
wx.setStorageSync("token", res.data.data.token);
let timer = setTimeout(() =>
wx.switchTab({
url: '/pages/index/index',
}), 2000)
});
} else {
console.log('登陸失敗!' + res.errMsg)
}
}
})
},
複製代碼
/pages/auth/auth
頁面使用的是wx.redirectTo({})
/pages/index/index
頁面使用的是wx.switchTab({})
/pages/index/index
是小程序tab頁,使用wx.redirectTo({})
沒法跳轉受權需制定button按鈕,加入open-type='getUserInfo'
屬性,bindgetuserinfo
調用自定義方法onGetUserInfo
。
<button class="auth-button" open-type='getUserInfo' bindgetuserinfo="onGetUserInfo">好的</button>
複製代碼
onGetUserInfo
接受受權狀態及受權獲取的用戶信息,再進行code
獲取,經過code
向後臺獲取用戶openId及token。
onGetUserInfo: function(e) {
console.log(e)
const userInfo = e.detail.userInfo;
if (userInfo) {
//經過`code`向後臺獲取用戶openId及token。
this._userLoginGetCode(userInfo);
}
},
複製代碼
在component
目錄下的images-button
組件,作了簡單的圖片插槽統一,在分享按鈕,用戶登陸按鈕,文件上傳按鈕都可以使用。plain="{{true}}"
表明button背景透明
<button open-type="{{openType}}" plain="{{true}}" class="container">
<slot name="img"></slot>
</button>
複製代碼
options
須要開啓插槽功能,添加multipleSlots: true
open-type="{{openType}}"
父組件將參數傳入子組件,子組件在properties
屬性中能夠獲取到父組件傳來的openType數據,經過this.properties.openType
能夠獲取屬性值
options: {
// 開啓插槽
multipleSlots: true
},
/**
* 組件的屬性列表
*/
properties: {
openType: {
type: String
}
},
複製代碼
index
頁面引入組件,須要在index.json
中添加組件路徑
{
"usingComponents": {
"btn-cmp": "/component/image-button/index"
}
}
複製代碼
主要使用到wx.chooseImage({})
進行圖片的選擇,選擇後使用wx.uploadFile({})
上傳圖片至服務器
//上傳文件
onUpload(event) {
let _this = this;
wx.chooseImage({
count: 1, // 默認9
sizeType: ['original', 'compressed'], // 能夠指定是原圖仍是壓縮圖,默認兩者都有
sourceType: ['album', 'camera'], // 能夠指定來源是相冊仍是相機,默認兩者都有
success: function(res) {
// 返回選定照片的本地文件路徑列表,tempFilePath能夠做爲img標籤的src屬性顯示圖片
let tempFilePaths = res.tempFilePaths;
console.log(tempFilePaths)
wx.uploadFile({
header: {
"Authorization": "Bearer " + wx.getStorageSync("token")
},
url: config.apiBaseUrl + '/file/upload',
filePath: tempFilePaths[0],
name: 'file',
success: (res) => {
wx.showToast({
title: "上傳成功~",
icon: 'none',
duration: 2000
})
},
fail: (res) => {
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 2000
})
}
})
}
})
}
複製代碼
固定列表頁搜索header位置,點擊header顯示search
組件,在search
組件點擊取消則隱藏search
組件,此處設計子組件向父組件傳遞消息
"usingComponents": {
...
"search-cmp": "/component/search/index"
}
複製代碼
search
組件的,默認爲false,在點擊header時更新searchPage爲true,顯示search
組件<view wx:if="{{!searchPage}}" class="container">
...
</view>
<search-cmp wx:if="{{searchPage}}" ></search-cmp>
複製代碼
search
頁面點擊取消,向父組件發送一個this.triggerEvent('cancel', {}, {});
事件,在xml中的search-cmp
添加cancel
事件的通知綁定#file頁面中的search-cmp組件
<search-cmp wx:if="{{searchPage}}" bind:cancel="onCancel"></search-cmp>
複製代碼
父組件file
頁面綁定子組件傳來的cancel
事件通知,就調用onCancel
方法, 在onCancel
方法中獲取事件響應,將searchPage
參數修改成false,search
組件就隱藏起來了
//cancel searching page
onCancel(event) {
console.info(event)
this.triggerEvent('cancel', {}, {});
},
複製代碼
在page
中的file頁面,獲取到後臺傳來的fileList數據,引入file組件,file="{{item}}"
將數據傳入子組件
<view wx:if="{{fileList}}">
<block wx:for="{{fileList}}" wx:key="{{item.id}}" file="{{item}}">
<file-cmp file="{{item}}" bind:del="onDelete"></file-cmp>
<view class="line"></view>
</block>
</view>
複製代碼
在component
中的file組件,在properties
添加屬性file
來接收父組件傳來的數據
/**
* 組件的屬性列表
*/
properties: {
file: Object
}
複製代碼
file組件在xml頁面中使用{{file.fileName}}
便可獲取到對象信息,相應的數據也會呈如今頁面上
<image src="images/copy.png" bindtap="onCopy"></image>
複製代碼
圖片點擊響應方法onCopy
,onCopy
調用wx.setClipboardData({})
能夠將數據複製到粘貼板
onCopy: function (event) {
console.info(event)
let _this = this;
wx.setClipboardData({
data: _this.properties.file.filePath,
success: function(res) {
wx.showToast({
title: '圖片地址複製成功',
})
}
});
複製代碼
<image src="images/del.png" bindtap="onDelete"></image>
複製代碼
子組件將數據傳遞給父組件,點擊刪除圖片出發onDelete
方法,經過this.triggerEvent('del', {fileId}, {});
將文件ID發送到父組件
onDelete: function (event) {
console.info(event)
let fileId = this.properties.file.id;
this.triggerEvent('del', {fileId}, {});
},
複製代碼
父組件file頁面綁定子組件傳來的del
事件
<file-cmp file="{{item}}" bind:del="onDelete"></file-cmp>
複製代碼
調用onDelete
出發網絡請求去完成刪除文件的邏輯,刪除成功後從新刷新文件列表
//刪除圖片
onDelete(event) {
console.info("DEL")
console.info(event)
let fileId = event.detail.fileId;
fileModel.delFileById(fileId).then((res) => {
console.info(res);
wx.showToast({
title: '刪除成功',
})
this.setData({
pageNum: 1,
fileList: []
});
this._getFileList();
})
},
複製代碼
小程序自帶用戶反饋功能,使用button
跳轉至網頁,用戶能夠填寫相關反饋,open-type
設置爲feedback
<button class="about-btn" plain="true" open-type="feedback">
<text class="about-btn-text">反饋建議</text>
</button>
複製代碼
小程序的button
中的open-type
擁有開放能力,在微信公衆平臺中啓用客服功能,添加客服人員,在代碼中添加button
便可彈出客服聊天界面,open-type
設置爲contact
<button class="about-btn" plain="true" open-type="contact" bindcontact="handleContact">
<text class="about-btn-text">聯繫客服</text>
</button>
複製代碼
此處使用插槽,button
中的open-type
設置爲share
<btn-cmp open-type="share">
<image slot="img" src="images/share.png" />
</btn-cmp>
複製代碼
小程序動畫官方文檔
開屏動畫,設置文字透明度,從0到1,漸漸顯示,主要使用到opacity
去設置組件的透明度,先建立一個動畫animationTip
,持續800ms,而後在setTimeout(function () {})
中設置動畫出現時間
var animationTip = wx.createAnimation({
//持續時間800ms
duration: 800,
timingFunction: 'ease',
});
this.animationTip = animationTip;
animationTip.opacity(0).step()
this.setData({
animationTip: animationTip.export()
})
setTimeout(function () {
animationTip.opacity(1).step()
this.setData({
animationTip: animationTip.export()
})
}.bind(this), 500)
複製代碼
utils
目錄下的config.apiBaseUrl
,改爲本身的域名,上傳到微信公衆號平臺,在版本管理中進行發佈const config ={
apiBaseUrl: "你本身的域名或服務器地址"
}
複製代碼
hi-straw
hi-straw
├── common -- 公共模塊
├── config -- 配置模塊
├── controller -- controller接口
├── core -- 核心業務模塊
| ├── annontaion -- 註解
| ├── aop -- aop實現
| ├── constant -- 常量
| ├── filter -- 攔截器
| ├── jwt -- jwt相關
| └── result -- 結果返回
├── entity -- 實體類
| ├── dto -- 數據傳輸
| └── vo -- 頁面傳輸
├── exception -- 全局異常
├── mapper -- dao層
├── service -- service層
└── util -- 工具類
複製代碼
CREATE TABLE `t_straw_file` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(11) UNSIGNED DEFAULT NULL COMMENT '用戶ID',
`file_path` varchar(255) DEFAULT NULL COMMENT '文件路徑',
`file_name` varchar(100) DEFAULT NULL COMMENT '文件名',
`file_size` varchar(10) DEFAULT NULL COMMENT '文件大小',
`props` varchar(255) DEFAULT NULL,
`status` tinyint(5) UNSIGNED DEFAULT '0' COMMENT '0.正常 -1.刪除',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT'建立時間',
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COMMENT '用戶文件列表';
複製代碼
CREATE TABLE `t_straw_user` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_name` varchar(20) DEFAULT NULL COMMENT '用戶名',
`nickname` varchar(20) DEFAULT NULL COMMENT '用戶暱稱',
`user_logo` varchar(250) DEFAULT NULL COMMENT '用戶logo',
`phone_num` varchar(20) DEFAULT NULL COMMENT '手機號',
`open_id` varchar(55) DEFAULT NULL COMMENT '微信openId',
`union_id` varchar(20) DEFAULT NULL COMMENT '微信union_id',
`password` varchar(50) DEFAULT NULL COMMENT '密碼',
`uuid` varchar(20) DEFAULT NULL COMMENT '自定義生成的uuid',
`last_login` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '最後登錄時間',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
PRIMARY KEY (`id`),
UNIQUE KEY `open_id` (`open_id`) USING HASH
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COMMENT '用戶表';
複製代碼
CREATE TABLE `t_straw_user_file_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) UNSIGNED NOT NULL COMMENT '用戶ID',
`file_size` int(11) UNSIGNED DEFAULT '0' COMMENT '用戶文件大小',
`left_size` int(11) UNSIGNED DEFAULT '5242880' COMMENT '剩餘文件大小',
`total_size` int(11)UNSIGNED DEFAULT '5242880' COMMENT '用戶文件空間大小',
`file_num` int(11) UNSIGNED DEFAULT '0' COMMENT '文件數量',
`is_vip` tinyint(5) UNSIGNED DEFAULT '0' COMMENT '是否爲vip,1.是 0.否',
PRIMARY KEY (`id`),
UNIQUE KEY `user_id` (`user_id`) USING HASH
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4 COMMENT'用戶文件信息';
複製代碼
CREATE TABLE `t_straw_user_info` (
`user_id` int(11) UNSIGNED NOT NULL COMMENT '用戶ID',
`sex` tinyint(5) UNSIGNED DEFAULT NULL COMMENT '性別',
`location` varchar(55) DEFAULT NULL COMMENT '位置',
`platform` varchar(55) DEFAULT NULL COMMENT '平臺',
`birthday` datetime DEFAULT NULL COMMENT '生日',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用戶詳細表';
複製代碼
代碼com.w77996.straw.util.QiNiuUtil
七牛雲官網申請帳號,得到AccessKey
,SecretKey
,並設置七牛雲圖片bucket
/**
* 七牛accessKey
*/
@Value("${QiNiu.accessKey}")
private String accessKey;
/**
* 七牛密鑰
*/
@Value("${QiNiu.secretKey}")
private String secretKey;
/**
* 七牛bucket
*/
@Value("${QiNiu.bucket}")
private String bucket;
複製代碼
pom.xml
文件引入七牛雲倉庫
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>[7.2.0, 7.2.99]</version>
</dependency>
複製代碼
/**
* 七牛雲生成token
*
* @param fileName
* @return
*/
public QiNiuAuth generateToken(String userId, String fileName) {
Auth auth = Auth.create(accessKey, secretKey);
String key = "upload/file/000/" + userId + "/" + fileName;
StringMap putPolicy = new StringMap();
putPolicy.put("returnBody", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"fsize\":$(fsize)}");
long expireSeconds = 3600;
String upToken = auth.uploadToken(bucket, key, expireSeconds, putPolicy);
Map<String, String> resultMap = Maps.newHashMapWithExpectedSize(3);
resultMap.put("domain", "https://www.w77996.cn");
resultMap.put("key", key);
resultMap.put("upToken", upToken);
return new QiNiuAuth("https://www.w77996.cn", key, upToken);
}
複製代碼
/**
* 上傳圖片
*
* @param file
* @param key
* @param token
* @return
*/
public String uploadImage(MultipartFile file, String key, String token) {
Configuration cfg = new Configuration(Zone.zone2());
UploadManager uploadManager = new UploadManager(cfg);
String filePath = null;
//生成上傳憑證,不指定key的狀況下,以文件內容的hash值做爲文件名
Response response = null;
try {
byte[] uploadBytes = file.getBytes();
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
response = uploadManager.put(uploadBytes, key, upToken);
//解析上傳成功的結果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
log.info("上傳結果 {} {}", putRet.hash, putRet.key);
filePath = putRet.key;
} catch (QiniuException ex) {
try {
response = ex.response;
log.error(response.bodyString());
} catch (QiniuException ex2) {
//ignore
ex.printStackTrace();
}
}
} catch (Exception ex) {
//ignore
ex.printStackTrace();
}
return filePath;
}
複製代碼
/**
* 刪除圖片
*
* @param key
*/
public void delete(String key) {
Configuration cfg = new Configuration(Zone.zone2());
Auth auth = Auth.create(accessKey, secretKey);
//實例化一個BucketManager對象
BucketManager bucketManager = new BucketManager(auth, cfg);
try {
//調用delete方法移動文件
bucketManager.delete(bucket, key);
} catch (QiniuException e) {
//捕獲異常信息
throw new GlobalException(ResultCode.ERROR);
}
}
複製代碼
代碼com.w77996.straw.core.annotation.Limiter
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limiter {
/**
*
* @return
*/
String value() default "";
/**
* 每秒向桶中放入令牌的數量 默認最大即不作限流
* @return
*/
double perSecond() default Double.MAX_VALUE;
/**
* 獲取令牌的等待時間 默認0
* @return
*/
int timeOut() default 0;
/**
* 超時時間單位
* @return
*/
TimeUnit timeOutUnit() default TimeUnit.MILLISECONDS;
}
複製代碼
代碼com.w77996.straw.core.aop.RateLimitAspect
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
private RateLimiter rateLimiter = RateLimiter.create(Double.MAX_VALUE);
/**
* 定義切點
* 一、經過掃包切入
* 二、帶有指定註解切入
*/
@Pointcut("@annotation(com.w77996.straw.core.annotation.Limiter)")
public void checkPointcut() {
}
@ResponseBody
@Around(value = "checkPointcut()")
public Object aroundNotice(ProceedingJoinPoint pjp) throws Throwable {
log.info("攔截到了{}方法...", pjp.getSignature().getName());
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
//獲取目標方法
Method targetMethod = methodSignature.getMethod();
if (targetMethod.isAnnotationPresent(Limiter.class)) {
//獲取目標方法的@Limiter註解
Limiter limiter = targetMethod.getAnnotation(Limiter.class);
rateLimiter.setRate(limiter.perSecond());
if (!rateLimiter.tryAcquire(limiter.timeOut(), limiter.timeOutUnit())) {
log.info("rateLimiter lock");
return Result.error(ResultCode.BUSY);
}
}
return pjp.proceed();
}
}
複製代碼
限定每秒只能調用一次,若是超出,則返回Result.error(ResultCode.BUSY)
@GetMapping("/limit")
@Limiter(perSecond = 1.0, timeOut = 500)
public String testLimiter() {
return " success";
}
複製代碼
使用JwtUtil生成jwt Token
/**
* 生成jwt
*
* @param userId
* @return
*/
public static String createJWT(String userId) {
String token = JwtHelper.createJWT(userId, Constant.JWT_CLIENT_ID,
Constant.JWT_NAME, Constant.JWT_EXPIRES_SECOND, Constant.JWT_BASE64_SECRET);
return token;
}
複製代碼
將userId放入token中,在請求接口時能夠經過請求Header獲取Bearer {token}進行解碼,從而獲取userId。
/**
* 經過token獲取用戶信息
*
* @return
*/
public String getUserIdByToken() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String accessToken = request.getHeader("Authorization");
if (StringUtils.isEmpty(accessToken) || accessToken.length() < 20) {
throw new GlobalException(ResultCode.ERROR_TOKEN_NULL);
}
accessToken = accessToken.substring(7);
if ("admin".equals(accessToken)) {
return "1";
}
Claims claims = JwtHelper.parseJWT(accessToken, Constant.JWT_BASE64_SECRET);
return claims.getSubject();
}
複製代碼
代碼 com.w77996.straw.core.annotation.IgnoreToken
先設置忽略token的註解
/**
* @description: 忽略token
* @author: w77996
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(value={ElementType.METHOD,ElementType.TYPE})
public @interface IgnoreToken {
}
複製代碼
代碼 com.w77996.straw.core.filter.TokenFilter
攔截器TokenFilter
實現HandlerInterceptor
,在每次請求進來時進行攔截,在調用controller以前都會調用perHandle
,因此在perHandler
內獲取方法名的註解,判斷是否有ignoreToken的註解,而後進行jwt的校驗。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
IgnoreToken ignoreToken = handlerMethod.getBeanType().getAnnotation(IgnoreToken.class);
log.info("enter preHandle {}",request.getRequestURL());
if (ignoreToken == null) {
ignoreToken = handlerMethod.getMethodAnnotation(IgnoreToken.class);
}
if (ignoreToken != null) {
log.info("ignoreToken not null");
return true;
}
log.info("ignoreToken null");
String token = request.getHeader("Authorization");
if(token != null){
log.info("token is {}",token);
if ("admin".equals(token.substring(7))) {
return true;
}
Claims claims = JwtHelper.parseJWT(token.substring(7), Audience.BASE64SECRET);
if(claims != null){
log.info("claims is {} {}",claims.toString(),claims.getSubject());
return true;
}else{
log.info("claims is null");
throw new GlobalException(ResultCode.ERROR_AUTH);
}
}
return false;
}
複製代碼
代碼com.w77996.straw.config.WebMvcAdapterConfig
不攔截/druid/*
的接口
/**
* @description: web攔截器
* @author: w77996
**/
@Component
public class WebMvcAdapterConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenFilter()).excludePathPatterns("/druid/*");
}
}
複製代碼
代碼com.w77996.straw.config.DruidConfig
項目運行後訪問http://ip:port/druid
,輸入帳號admin
密碼amdin
便可訪問
@Configuration
public class DruidConfig {
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource druidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
return dataSource;
}
@Bean
public ServletRegistrationBean druidStatViewServlet() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
// IP白名單 (沒有配置或者爲空,則容許全部訪問)e
registrationBean.addInitParameter("allow", "");
// IP黑名單 (存在共同時,deny優先於allow)
registrationBean.addInitParameter("deny", "");
registrationBean.addInitParameter("loginUsername", "admin");
registrationBean.addInitParameter("loginPassword", "admin");
registrationBean.addInitParameter("resetEnable", "false");
return registrationBean;
}
@Bean
public FilterRegistrationBean druidWebStatViewFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(new WebStatFilter());
registrationBean.addInitParameter("urlPatterns", "/*");
registrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
return registrationBean;
}
}
複製代碼
全局異常攔截主要是依靠@RestControllerAdvice
註解,在方法上使用@ExceptionHandler(value = Exception.class)
表明攔截全部Exception,而後進行對應的操做
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 全局錯誤攔截
*
* @param e
* @return
*/
@ExceptionHandler(value = Exception.class)
private Result<Object> exceptionHandler(Exception e) {
if (e instanceof GlobalException) {
GlobalException ex = (GlobalException) e;
return Result.error(ex.getCode());
}
return Result.error(ResultCode.ERROR.getCode(),e.getMessage());
}
}
複製代碼
須要在微信公共平臺獲取對應的appId,appSec,小程序獲取到code以後發送給後臺,後臺獲取code向微信發送http請求,使用的是restTemplate,可是須要注意編碼,微信編碼返回是ISO-8859-1;調用成功後能夠拿到用戶的openId,再去數據庫中獲取對應的用戶信息,進行登錄更新及用戶建立的邏輯處理
@RestController
@RequestMapping("/wx")
@Slf4j
public class WxController {
@Autowired
private IUserService iUserService;
@Value("${wx.appId}")
private String wxAppId;
@Value("${wx.appSec}")
private String wxAppSec;
/**
* 經過code獲取openId
*
* @param wxLoginDto
* @return
*/
@IgnoreToken
@PostMapping("/code")
public Result getUserInfoByCode(@RequestBody WxLoginDto wxLoginDto) {
log.info("enter getUserInfoByCode");
//微信受權獲取openId
String reqUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + wxAppId + "&secret=" + wxAppSec + "&js_code=" + wxLoginDto.getCode() + "&grant_type=authorization_code";
JSONObject wxAuthObject = RestHttpClient.client(reqUrl, HttpMethod.GET, null);
log.info("wxAuthObject {}", wxAuthObject.toJSONString());
WxTokenDto wxTokenDto = JSONObject.parseObject(wxAuthObject.toJSONString(), WxTokenDto.class);
log.info("wxTokenDto {}", wxTokenDto.toString());
Map<String, Object> tokenMapper = Maps.newHashMapWithExpectedSize(2);
//生成新用戶
UserEntity userEntity = iUserService.getUserByOpenId(wxTokenDto.getOpenid());
if (!ObjectUtils.allNotNull(userEntity)) {
WxUserInfoDto wxUserInfoDto = new WxUserInfoDto();
wxUserInfoDto.setNickname(wxLoginDto.getNickName());
wxUserInfoDto.setUserLogo(wxLoginDto.getUserLogo());
wxUserInfoDto.setSex(wxLoginDto.getSex());
wxUserInfoDto.setLastLogin(new Date());
wxUserInfoDto.setOpenId(wxTokenDto.getOpenid());
wxUserInfoDto.setLocation(StringUtils.join(new String[]{wxLoginDto.getCountry(), wxLoginDto.getProvince(), wxLoginDto.getCity()}, "-"));
iUserService.createNewUser(wxUserInfoDto);
log.info("create new user {}", wxUserInfoDto);
}
tokenMapper.put("token", JwtHelper.createJWT(userEntity.getId() + ""));
return Result.success(tokenMapper);
}
}
複製代碼
項目環境分爲dev
和prod
兩種,resource
文件下默認加載application.yml
。
application.yml
中spring:
profiles:
active: @spring.profiles.active@
複製代碼
@spring.profiles.active@
對應的爲pom.xml文件中profiles下的spring.profiles.active
屬性
默認狀況下使用dev
環境下的配置信息
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<!-- default Spring profiles -->
<spring.profiles.active>dev</spring.profiles.active>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<!-- default Spring profiles -->
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
複製代碼
prod
環境:執行mvn package -Pprod -DskipTests
dev
環境:執行mvn package -Pdev -DskipTests
在properties
屬性中添加時間格式,而後再build
中添加fileName
格式化文件名。
<artifactId>hi-straw</artifactId>
<version>1.0.0</version>
<properties>
...
<maven.build.timestamp.format>yyyy-MM-ddHHmm</maven.build.timestamp.format>
</properties>
<build>
...
<finalName>
${project.artifactId}-${project.version}-${spring.profiles.active}_${maven.build.timestamp}
</finalName>
</build>
複製代碼
打包完成後生成的jar:hi-straw-1.0.0-prod_2019-04-091533.jar
登錄服務器,clone項目至/root/repo_git/
目錄下,執行進入script
目錄下,執行./build.sh
,須要將RELEASE_HOST
換成你本身的服務器地址,方便作保存備份
#!/bin/sh
set -e
#打包的服務器地址
RELEASE_HOST="你本身的服務器地址"
#打包的環境
RELEASE_ENV=prod
#項目目錄
BASE_DIR=/root/repo_git/Histraw
#進入項目目錄
cd ${BASE_DIR}
#執行git拉去最新的代碼
echo "pulling changes..."
git pull origin master
echo "pulling changes... finish"
echo "building..."
#執行mvn命令打包
mvn clean
mvn package -P${RELEASE_ENV} -DskipTests docker:build
echo "building...finish"
echo "env =${RELEASE_ENV}"
#for HOST in ${RELEASE_HOST}; do
#進行拷貝及備份
RELEASE_TARGET=root@${RELEASE_HOST}:~/release/
echo "copying to $RELEASE_TARGET..."
scp ${BASE_DIR}/target/*.jar ${RELEASE_TARGET}
echo "copying to $RELEASE_TARGET...done"
#done
複製代碼
docker images
查看剛剛打包好的docker鏡像
卸載老舊的版本(若未安裝過可省略此步):
sudo apt-get remove docker docker-engine docker.io
複製代碼
安裝最新的docker:
curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh
複製代碼
確認Docker成功安裝:
docker run hello-world
複製代碼
在src/main/docker
下創建dockerFile
文件
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD *.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
複製代碼
pom.xml配置docker打包,配合shell腳本在linux實現maven自動打包docker
<!-- Docker maven plugin -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<imageName>${project.artifactId}</imageName>
<dockerDirectory>src/main/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
<!-- Docker maven plugin -->
複製代碼
執行docker images
查看剛剛打包的docker鏡像
執行docker run --name hi-straw -p 8989:8989 -t hi-straw
啓動鏡像
執行dockers ps
查看已啓動docker鏡像
登錄到服務器,執行
$ apt-get update // 更新軟件
$ apt-get install nginx // 安裝nginx
複製代碼
能夠去阿里雲獲取免費證書
將生成的證書放入/etc/nginx/sites-enabled/cert/
(具體看你將nginx安裝在哪一個目錄下)
新建一個https.conf
server {
listen 443;
server_name 你本身的域名;
ssl on;
ssl_certificate cert/你本身的證書.pem;
ssl_certificate_key cert/你本身的證書.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
#項目部署的ip和端口
proxy_pass http://localhost:port;
}
}
複製代碼
配置完成後,檢查一下nginx配置文件是否可用
nginx -t //檢查nginx配置文件
複製代碼
配置正確後,從新加載配置文件使配置生效
nginx -s reload //使配置生效
複製代碼
如需重啓nginx,用如下命令:
service nginx stop //中止
service nginx start //啓動
service nginx restart //重啓
複製代碼
修改resource
下的application-dev.yml
和application-prod.yml
中你本身申請的微信息及七牛雲信息修改,修改數據庫地址,用戶名和密碼
#七牛
qiNiu:
accessKey: 你申請的七牛雲key
secretKey: 你申請的七牛雲sec
bucket: 你申請的七牛雲bucket
domain: 你申請的七牛雲domain
#微信
wx:
appId: 你申請的微信id
appSec: 你申請的微信sec
複製代碼
spring:
datasource:
name: graduate
driver-class-name: com.mysql.jdbc.Driver
url: 數據庫地址
username: 數據庫用戶名
password: 數據庫密碼
複製代碼
修改script
目錄下build.sh
RELEASE_HOST="你本身的服務器地址"
複製代碼
項目的服務器7月15號到期了……哪位大佬資助一下服務器,感激涕零
捐助