[TOC]
Артефакт кровати в виде чучела
Сервер устарел, и проект недоступен, пожалуйста, поймите
Адрес источника:
小程序源码 https://github.com/w77996/mini-straw
后台源码 https://github.com/w77996/hi-straw
испытать это
Раньше я играл в перерывах между сменой работы.С одной стороны, я практиковал апплет. После того, как я его выучил, я им почти не пользовался. С другой стороны, я писал какие-то заметки. Я работал над проектом с перерывами в течение двух месяцев. Это всего лишь простой инструмент для загрузки изображений. Не забудьте поставить звезду
Апплеты
- авторизация апплета, логин
- Родительский и дочерний компоненты взаимодействуют друг с другом
- Обмен небольшими программами, комментарии и предложения, функции обслуживания клиентов, загрузка файлов
- Использование гибкой верстки
- Использование Promise, инкапсуляция бизнес-модели
- Использование слотов, анимационные эффекты
Структура каталогов проекта
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 + jwt
- Вход в WeChat, авторизация jwt
- Аннотации и использование АОП
- Мультисредовая упаковка Maven, использование докеров
- сценарий оболочки для автоматизации развертывания
- Обратный прокси nginx и конфигурация https
- Операция с облачным файлом Qiniu
Структура каталогов проекта
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]
mini-straw
Структура проекта
mini-straw
├── component -- 组件
| ├── file -- 文件组件
| ├── image-button -- 图片按钮组件
| ├── search -- 查找页面组件
| ├── tag -- 标签组件
├── images -- 图片目录
| ├── icon -- icon图片
| ├── tab -- tab图片
├── model -- 封装的model
├── pages -- 页面
| ├── about -- 关于页
| ├── auth -- 授权页
| ├── file -- 文件页
| ├── index -- 首页
| ├── launch -- 启动页面
| ├── my -- 个人中心
└── utils -- 工具
открытие страницы
1. Определите состояние сети
использовать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;
}
},
})
2. Определяем статус авторизации
использовать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({})Получить информацию о пользователе WeChat, позвонить после завершения сбора информацииwx.login({})получить апплетcode,пройти черезcodeПолучите openId и токен пользователя в фоновом режиме.
//后台获取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)
}
}
})
},
3. Страница перехода
- Прыжок
/pages/auth/authСтраница используетwx.redirectTo({}) - Прыжок
/pages/index/indexСтраница используетwx.switchTab({})
так как/pages/index/indexэто вкладка апплета, используйтеwx.redirectTo({})не могу прыгать
Страница авторизации
Авторизация нужна для оформления кнопки button, joinopen-type='getUserInfo'Атрибуты,bindgetuserinfoвызвать пользовательский методonGetUserInfo.
<button class="auth-button" open-type='getUserInfo' bindgetuserinfo="onGetUserInfo">好的</button>
onGetUserInfoПримите статус авторизации и информацию о пользователе, полученную при авторизации, а затем продолжитеcodeпройти черезcodeПолучите openId и токен пользователя в фоновом режиме.
onGetUserInfo: function(e) {
console.log(e)
const userInfo = e.detail.userInfo;
if (userInfo) {
//通过`code`向后台获取用户openId及token。
this._userLoginGetCode(userInfo);
}
},
Домой
1. Слот для кнопки с изображением в сборе
существуетcomponentв каталогеimages-buttonКомпонент имеет простой унифицированный слот для изображений, который можно использовать в кнопке «Поделиться», кнопке входа пользователя и кнопке загрузки файла.plain="{{true}}"Представляет прозрачный фон кнопки
<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"
}
}
2. Загрузить файл
в основном используется для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
})
}
})
}
})
}
Список
1. Показать и скрыть компонент поиска
Фиксированная позиция заголовка поиска на странице списка, щелкните заголовок, чтобы отобразитьsearchкомпоненты, вsearchКомпонент скрыт при нажатии кнопки «Отмена»searchКомпоненты, где дочерние компоненты предназначены для передачи сообщений родительским компонентам.
- Внедрить компонент поиска
"usingComponents": {
...
"search-cmp": "/component/search/index"
}
- Используйте параметр searchPage для оценки
searchКомпонент, значение по умолчанию — false, при нажатии на заголовок обновите searchPage до true, отобразитеsearchкомпоненты
<view wx:if="{{!searchPage}}" class="container">
...
</view>
<search-cmp wx:if="{{searchPage}}" ></search-cmp>
-
searchСтраница нажимает кнопку «Отмена» и отправляетthis.triggerEvent('cancel', {}, {});Событие, в XMLsearch-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', {}, {});
},
2. Список файлов
1. Получите информацию о списке и передайте ее файловому компоненту.
существуетpageНа странице файла получите данные fileList из фона и введите файловый компонент,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файловый компонент вpropertiesдобавить свойстваfileполучать данные от родительского компонента
/**
* 组件的属性列表
*/
properties: {
file: Object
}
Файловый компонент используется на странице xml{{file.fileName}}Информация об объекте может быть получена, и соответствующие данные также будут представлены на странице.
3. Операция вставки платы
<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: '图片地址复制成功',
})
}
});
4. Удалить операцию
<image src="images/del.png" bindtap="onDelete"></image>
Дочерний компонент передает данные родительскому компоненту, нажмите, чтобы удалить изображение, чтобы начатьonDeleteметод, черезthis.triggerEvent('del', {fileId}, {});Отправить идентификаторы файла на родительские компоненты
onDelete: function (event) {
console.info(event)
let fileId = this.properties.file.id;
this.triggerEvent('del', {fileId}, {});
},
Страница файла родительского компонента привязана к дочернему компоненту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();
})
},
моя страница
1. Комментарии и предложения
Апплет имеет свою собственную функцию обратной связи пользователей.buttonПерейти на веб-страницу, пользователи могут оставить соответствующий отзыв,open-typeУстановить какfeedback
<button class="about-btn" plain="true" open-type="feedback">
<text class="about-btn-text">反馈建议</text>
</button>
2. Обслуживание клиентов мини-программы
апплетbuttonсерединаopen-typeС открытой возможностью включить функцию обслуживания клиентов на общедоступной платформе WeChat, добавить персонал службы поддержки клиентов, добавить кодbuttonПоявится интерфейс чата службы поддержки клиентов,open-typeУстановить какcontact
<button class="about-btn" plain="true" open-type="contact" bindcontact="handleContact">
<text class="about-btn-text">联系客服</text>
</button>
3. Совместное использование мини-программ
Здесь используются слоты,buttonсерединаopen-typeУстановить какshare
<btn-cmp open-type="share">
<image slot="img" src="images/share.png" />
</btn-cmp>
анимация
Мини программа Анимация Официальная документация
Открыть анимацию экрана, установить прозрачность текста от 0 до 1, постепенно отображать, в основном используется дляopacityЧтобы установить прозрачность компонента, сначала создайте анимациюanimationTip, в течение 800 мс, а затем в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, Измените его на собственное доменное имя, загрузите его на платформу общедоступной учетной записи WeChat и опубликуйте в управлении версиями.
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 '用户详细表';
Подробный код
Предварительная подготовка
- Сервер Linux и база данных MySql
- Облачный аккаунт Qiniu, вы можете перейти наОфициальный сайт Qiniu CloudПодать заявку на учетную запись
- HTTPS-сертификатAlibaba Cloud получает бесплатный сертификат
- Учетная запись разработчика апплета общедоступной платформы WeChat
Загрузка файла в облако Qiniu
кодcom.w77996.straw.util.QiNiuUtil
1. Приложение облачной учетной записи Qiniu
Официальный сайт Qiniu CloudПодать заявку на аккаунт, получитьAccessKey,SecretKey, и установите изображение облака семи коровbucket
/**
* 七牛accessKey
*/
@Value("${QiNiu.accessKey}")
private String accessKey;
/**
* 七牛密钥
*/
@Value("${QiNiu.secretKey}")
private String secretKey;
/**
* 七牛bucket
*/
@Value("${QiNiu.bucket}")
private String bucket;
2. Представлен Qiniu Cloud SDK
pom.xmlИмпорт файлов в Qiniu Cloud Warehouse
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>[7.2.0, 7.2.99]</version>
</dependency>
2. Генерация токена Qiniuyun
/**
* 七牛云生成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);
}
3. Загрузите код файла
/**
* 上传图片
*
* @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;
}
4. Удалить картинку
/**
* 删除图片
*
* @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);
}
}
Аннотация + ограничение тока интерфейса АОП
1. Написание аннотации
код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;
}
2. Реализация АОП
кодcom.w77996.straw.core.aop.RateLimitAspect
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
private RateLimiter rateLimiter = RateLimiter.create(Double.MAX_VALUE);
/**
* 定义切点
* 1、通过扫包切入
* 2、带有指定注解切入
*/
@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();
}
}
3. Использование аннотации
Лимит может быть вызван только один раз в секунду, если он превышается, он вернетсяResult.error(ResultCode.BUSY)
@GetMapping("/limit")
@Limiter(perSecond = 1.0, timeOut = 500)
public String testLimiter() {
return " success";
}
Реализация JWT
1. поколение jwt
Используйте JwtUtil для создания токена jwt
/**
* 生成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;
}
2. Токен анализируется в userId
Поместите userId в токен, и вы можете получить Bearer через заголовок запроса при запросе интерфейса.{токен} декодируется для получения идентификатора пользователя.
/**
* 通过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();
}
3. Метод перехватчика + аннотации для аутентификации токена
кодcom.w77996.straw.core.annotation.IgnoreToken
Сначала установите аннотацию, чтобы игнорировать токен
/**
* @description: 忽略token
* @author: w77996
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(value={ElementType.METHOD,ElementType.TYPE})
public @interface IgnoreToken {
}
кодcom.w77996.straw.core.filter.TokenFilter
перехватчикTokenFilterвыполнитьHandlerInterceptor, чтобы перехватывать каждый раз, когда приходит запрос, он будет вызываться перед вызовом контроллера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;
}
4. Внедрить веб-перехватчик
код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)Представляет перехват всех исключений и последующее выполнение соответствующих операций.
@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());
}
}
Вход в WeChat
Соответствующие appId и appSec должны быть получены на общедоступной платформе WeChat.После того, как апплет получает код, он отправляется в фоновый режим.Фон получает код и отправляет HTTP-запрос в WeChat.Используется RestTemplate, но следует обратить внимание платная кодировка. Кодировка WeChat возвращает 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);
}
}
spring boot + мультисредовая упаковка maven
1. файл yml в ресурсе
Среда проекта делится наdevиprodдва,resourceЗагружается по умолчанию под файлapplication.yml.
- среда разработки: application-dev.yml
- производственная среда: application-prod.yml
существует
application.ymlсередина
spring:
profiles:
active: @spring.profiles.active@
@spring.profiles.active@Соответствующее находится в профилях в файле pom.xml.spring.profiles.activeАтрибуты
2.pom.xml конфигурация
используется по умолчанию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>
3. Упаковка в различных средах
- Бэйл
prodОкружающая среда: выполнитьmvn package -Pprod -DskipTests - Бэйл
devОкружающая среда: выполнитьmvn package -Pdev -DskipTests
4. Упаковка проекта и нейминг
существует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>
Банка, созданная после завершения упаковки:hi-straw-1.0.0-prod_2019-04-091533.jar
сценарии оболочки
Войдите на сервер, клонируйте проект на/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 просто упакованоразвертывание пакета maven + docker
1. установка докерной среды
Удалите старую версию (если она не была установлена, этот шаг можно пропустить):
sudo apt-get remove docker docker-engine docker.io
Установите последний докер:
curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh
Подтвердите, что Docker был успешно установлен:
docker run hello-world
2. Скомпилируйте и упакуйте проект
существует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 настраивает упаковку докеров и взаимодействует со сценарием оболочки для реализации автоматической упаковки докеров maven в linux.
<!-- 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 run --name hi-straw -p 8989:8989 -t hi-strawзагрузочный образ
воплощать в жизньdockers psПросмотр запущенных образов докеров
nginx настроить https
1. Установите нгинкс
Войдите на сервер, выполните
$ apt-get update // 更新软件
$ apt-get install nginx // 安装nginx
2. Получить сертификат
можешь идтиAlibaba Cloud получает бесплатный сертификат
Поместите сгенерированный сертификат в/etc/nginx/sites-enabled/cert/(Это зависит от того, в какой каталог вы установили nginx)
3. Настройте файл 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, которую вы применили для себя, измените адрес базы данных, имя пользователя и пароль.
#七牛
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="你自己的服务器地址"
Срок действия сервера проекта истек 15 июля... Какой большой парень будет финансировать сервер, большое спасибо
пожертвовать