[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 июля... Какой большой парень будет финансировать сервер, большое спасибо
пожертвовать