Sistema de oficina de Yunshang: cuenta oficial de WeChat
Acceso directo a la estación B [como Silicon Valley] :
https://www.bilibili.com/video/BV1Ya411S7aT
Esta publicación de blog se publica principalmente relacionada con el curso e incorpora algunas de mis propias opiniones y brinda soluciones relevantes a los problemas encontrados en el proceso de aprendizaje. ¡Aprender juntos y progresar juntos! ! !
Aquí hay una guía. Si tiene un nombre de dominio, puede usar el nombre de dominio directamente. Pronto, si no tiene uno, este artículo presentará una muy buena tecnología: la penetración de la intranet y el desarrollo de la cuenta oficial de WeChat. Esperando el próximo estudio.
Directorio de artículos
- Sistema de oficina de Yunshang: cuenta oficial de WeChat
-
- 1. Descripción de la función
- 2. Gestión del menú de la cuenta oficial
- 3. Inicio de sesión autorizado de WeChat
-
- 1. Desarrollo de interfaz del lado del servidor
-
- 1.1 Configurar la penetración de la intranet (ngrok)
- 1.2 Configurar el "nombre de dominio de la página de devolución de llamada autorizada"
- 1.3 Configure la devolución de llamada de autorización para obtener la dirección de la interfaz de información del usuario
- 1,4, interfaz de controlador
- 1.5, excluir interceptación
- 2. Procesamiento front-end móvil
- 4. Impulso de noticias
1. Descripción de la función
Los empleados usan la cuenta oficial de WeChat para completar la operación de aprobación, y las funciones involucradas incluyen: menú personalizado, inicio de sesión autorizado, mensaje
1. El menú de primer nivel de la cuenta oficial de WeChat es: Lista de aprobación, Centro de aprobación, Mi
2. Los empleados prestan atención a la cuenta oficial, el empleado inicia sesión en la cuenta oficial de WeChat por primera vez y vincula la cuenta del empleado a través del inicio de sesión autorizado de WeChat
3. Los empleados envían información de aprobación y aprobación a través de la cuenta oficial de WeChat, y el sistema envía la información de aprobación de acuerdo con la cuenta oficial de WeChat y retroalimenta el proceso de aprobación de manera oportuna
Captura de pantalla del proyecto:
2. Gestión del menú de la cuenta oficial
Menú de primer nivel de la cuenta oficial, inicialización predeterminada de la base de datos (lista de aprobación, centro de aprobación, mina)
El efecto de página es el siguiente:
1. Menú de gestión CRUD
1.1, mapeador
package com.atguigu.wechat.mapper;
import com.atguigu.model.wechat.Menu;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MenuMapper extends BaseMapper<Menu> {
}
1.2, interfaz de servicio
package com.atguigu.wechat.service;
import com.atguigu.model.wechat.Menu;
import com.atguigu.vo.wechat.MenuVo;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface MenuService extends IService<Menu> {
List<MenuVo> findMenuInfo();
}
1.3 Implementación de la interfaz de servicio
package com.atguigu.wechat.service.impl;
import com.atguigu.model.wechat.Menu;
import com.atguigu.vo.wechat.MenuVo;
import com.atguigu.wechat.mapper.MenuMapper;
import com.atguigu.wechat.service.MenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
@Autowired
private MenuMapper menuMapper;
@Override
public List<MenuVo> findMenuInfo() {
List<MenuVo> list = new ArrayList<>();
List<Menu> menuList = menuMapper.selectList(null);
List<Menu> oneMenuList = menuList.stream().filter(menu -> menu.getParentId().longValue() == 0).collect(Collectors.toList());
for (Menu oneMenu : oneMenuList) {
MenuVo oneMenuVo = new MenuVo();
BeanUtils.copyProperties(oneMenu, oneMenuVo);
List<Menu> twoMenuList = menuList.stream()
.filter(menu -> menu.getParentId().longValue() == oneMenu.getId())
.sorted(Comparator.comparing(Menu::getSort))
.collect(Collectors.toList());
List<MenuVo> children = new ArrayList<>();
for (Menu twoMenu : twoMenuList) {
MenuVo twoMenuVo = new MenuVo();
BeanUtils.copyProperties(twoMenu, twoMenuVo);
children.add(twoMenuVo);
}
oneMenuVo.setChildren(children);
list.add(oneMenuVo);
}
return list;
}
}
1,4, interfaz de controlador
package com.atguigu.wechat.controller;
import com.atguigu.common.result.Result;
import com.atguigu.model.wechat.Menu;
import com.atguigu.wechat.service.MenuService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/wechat/menu")
@Slf4j
public class MenuController {
@Autowired
private MenuService menuService;
//@PreAuthorize("hasAuthority('bnt.menu.list')")
@ApiOperation(value = "获取")
@GetMapping("get/{id}")
public Result get(@PathVariable Long id) {
Menu menu = menuService.getById(id);
return Result.ok(menu);
}
//@PreAuthorize("hasAuthority('bnt.menu.add')")
@ApiOperation(value = "新增")
@PostMapping("save")
public Result save(@RequestBody Menu menu) {
menuService.save(menu);
return Result.ok();
}
//@PreAuthorize("hasAuthority('bnt.menu.update')")
@ApiOperation(value = "修改")
@PutMapping("update")
public Result updateById(@RequestBody Menu menu) {
menuService.updateById(menu);
return Result.ok();
}
//@PreAuthorize("hasAuthority('bnt.menu.remove')")
@ApiOperation(value = "删除")
@DeleteMapping("remove/{id}")
public Result remove(@PathVariable Long id) {
menuService.removeById(id);
return Result.ok();
}
//@PreAuthorize("hasAuthority('bnt.menu.list')")
@ApiOperation(value = "获取全部菜单")
@GetMapping("findMenuInfo")
public Result findMenuInfo() {
return Result.ok(menuService.findMenuInfo());
}
}
2. Implementación frontal
2.1 Definir la interfaz api
Crear src/api/wechat/menu.js
import request from '@/utils/request'
const api_name = '/admin/wechat/menu'
export default {
findMenuInfo() {
return request({
url: `${
api_name}/findMenuInfo`,
method: `get`
})
},
save(menu) {
return request({
url: `${
api_name}/save`,
method: `post`,
data: menu
})
},
getById(id) {
return request({
url: `${
api_name}/get/${
id}`,
method: `get`
})
},
updateById(menu) {
return request({
url: `${
api_name}/update`,
method: `put`,
data: menu
})
},
removeById(id) {
return request({
url: `${
api_name}/remove/${
id}`,
method: 'delete'
})
}
}
2.2 Realización de la página
Crear vistas/wechat/menu/list.vue
<template>
<div class="app-container">
<!-- 工具条 -->
<div class="tools-div">
<el-button class="btn-add" size="mini" @click="add">添 加</el-button>
</div>
<el-table
:data="list"
style="width: 100%;margin-bottom: 20px;"
row-key="id"
border
default-expand-all
:tree-props="{children: 'children'}">
<el-table-column label="名称" prop="name" width="350"></el-table-column>
<el-table-column label="类型" width="100">
<template slot-scope="scope">
{
{ scope.row.type == 'view' ? '链接' : scope.row.type == 'click' ? '事件' : '' }}
</template>
</el-table-column>
<el-table-column label="菜单URL" prop="url" ></el-table-column>
<el-table-column label="菜单KEY" prop="meunKey" width="130"></el-table-column>
<el-table-column label="排序号" prop="sort" width="70"></el-table-column>
<el-table-column label="操作" width="170" align="center">
<template slot-scope="scope">
<el-button v-if="scope.row.parentId > 0" type="text" size="mini" @click="edit(scope.row.id)">修改</el-button>
<el-button v-if="scope.row.parentId > 0" type="text" size="mini" @click="removeDataById(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%" >
<el-form ref="flashPromotionForm" label-width="150px" size="small" style="padding-right: 40px;">
<el-form-item label="选择一级菜单">
<el-select
v-model="menu.parentId"
placeholder="请选择">
<el-option
v-for="item in list"
:key="item.id"
:label="item.name"
:value="item.id"/>
</el-select>
</el-form-item>
<el-form-item label="菜单名称">
<el-input v-model="menu.name"/>
</el-form-item>
<el-form-item label="菜单类型">
<el-radio-group v-model="menu.type">
<el-radio label="view">链接</el-radio>
<el-radio label="click">事件</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="menu.type == 'view'" label="链接">
<el-input v-model="menu.url"/>
</el-form-item>
<el-form-item v-if="menu.type == 'click'" label="菜单KEY">
<el-input v-model="menu.meunKey"/>
</el-form-item>
<el-form-item label="排序">
<el-input v-model="menu.sort"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false" size="small">取 消</el-button>
<el-button type="primary" @click="saveOrUpdate()" size="small">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import menuApi from '@/api/wechat/menu'
const defaultForm = {
id: null,
parentId: 1,
name: '',
nameId: null,
sort: 1,
type: 'view',
meunKey: '',
url: ''
}
export default {
// 定义数据
data() {
return {
list: [],
dialogVisible: false,
menu: defaultForm,
saveBtnDisabled: false
}
},
// 当页面加载时获取数据
created() {
this.fetchData()
},
methods: {
// 调用api层获取数据库中的数据
fetchData() {
console.log('加载列表')
menuApi.findMenuInfo().then(response => {
this.list = response.data
console.log(this.list)
})
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定,远程调用ajax
return menuApi.removeById(id)
}).then((response) => {
this.fetchData(this.page)
this.$message.success(response.message || '删除成功')
}).catch(() => {
this.$message.info('取消删除')
})
},
// -------------
add() {
this.dialogVisible = true
this.menu = Object.assign({}, defaultForm)
},
edit(id) {
this.dialogVisible = true
this.fetchDataById(id)
},
fetchDataById(id) {
menuApi.getById(id).then(response => {
this.menu = response.data
})
},
saveOrUpdate() {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.menu.id) {
this.saveData()
} else {
this.updateData()
}
},
// 新增
saveData() {
menuApi.save(this.menu).then(response => {
this.$message.success(response.message || '操作成功')
this.dialogVisible = false
this.fetchData(this.page)
})
},
// 根据id更新记录
updateData() {
menuApi.updateById(this.menu).then(response => {
this.$message.success(response.message || '操作成功')
this.dialogVisible = false
this.fetchData(this.page)
})
}
}
}
</script>
3. Pulsar menú
Después de configurar el menú en segundo plano, lo enviaremos a la plataforma pública de WeChat
3.1 Solicitar una cuenta
El sistema de oficina de Yunshang no tiene funciones avanzadas como el pago de WeChat, por lo que no es necesario usar una cuenta de servicio y la prueba se puede completar usando una cuenta de prueba.
Usamos la "cuenta de prueba de la interfaz de la plataforma pública de WeChat", la dirección de la aplicación: https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login, después de tener una cuenta formal, simplemente cambie directamente
Escanee e inicie sesión para obtener la información del número de prueba: appID y appsecret
Vea la documentación de la API del "menú personalizado":
https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
Hay dos formas de implementar el menú push:
1. Siga completamente el método http del documento de la interfaz, pero este método es engorroso
2. Use la herramienta weixin-java-mp. Esta es una herramienta empaquetada que se puede usar directamente, lo cual es conveniente y rápido. Usaremos este método para desarrollar en el futuro.
3.2 Añadir configuración
Agregar configuración en application-dev.yml
wechat:
mpAppId: wx13db7dcf69bc1223
mpAppSecret: de3d7888d30febf84b64d0e6571e4027
3.3 Método de la herramienta
3.3.1 Introducción de dependencias
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.1.0</version>
</dependency>
3.3.2 Agregar herramientas y clases de configuración
Herramientas
package com.atguigu.wechat.config;
import lombok.Data;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {
private String mpAppId;
private String mpAppSecret;
}
clase de configuración
package com.atguigu.wechat.config;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class WeChatMpConfig {
@Autowired
private WechatAccountConfig wechatAccountConfig;
@Bean
public WxMpService wxMpService(){
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
return wxMpService;
}
@Bean
public WxMpConfigStorage wxMpConfigStorage(){
WxMpDefaultConfigImpl wxMpConfigStorage = new WxMpDefaultConfigImpl();
wxMpConfigStorage.setAppId(wechatAccountConfig.getMpAppId());
wxMpConfigStorage.setSecret(wechatAccountConfig.getMpAppSecret());
return wxMpConfigStorage;
}
}
3.3.3, implementación de interfaz push
Clase de operación: MenuServiceImpl
@Autowired
private WxMpService wxMpService;
@Override
public void syncMenu() {
List<MenuVo> menuVoList = this.findMenuInfo();
//菜单
JSONArray buttonList = new JSONArray();
for(MenuVo oneMenuVo : menuVoList) {
JSONObject one = new JSONObject();
one.put("name", oneMenuVo.getName());
if(CollectionUtils.isEmpty(oneMenuVo.getChildren())) {
one.put("type", oneMenuVo.getType());
one.put("url", "http://oa.atguigu.cn/#"+oneMenuVo.getUrl());
} else {
JSONArray subButton = new JSONArray();
for(MenuVo twoMenuVo : oneMenuVo.getChildren()) {
JSONObject view = new JSONObject();
view.put("type", twoMenuVo.getType());
if(twoMenuVo.getType().equals("view")) {
view.put("name", twoMenuVo.getName());
//H5页面地址
view.put("url", "http://oa.atguigu.cn#"+twoMenuVo.getUrl());
} else {
view.put("name", twoMenuVo.getName());
view.put("key", twoMenuVo.getMeunKey());
}
subButton.add(view);
}
one.put("sub_button", subButton);
}
buttonList.add(one);
}
//菜单
JSONObject button = new JSONObject();
button.put("button", buttonList);
try {
wxMpService.getMenuService().menuCreate(button.toJSONString());
} catch (WxErrorException e) {
throw new RuntimeException(e);
}
}
3.3.4, interfaz del controlador
//@PreAuthorize("hasAuthority('bnt.menu.syncMenu')")
@ApiOperation(value = "同步菜单")
@GetMapping("syncMenu")
public Result createMenu() {
menuService.syncMenu();
return Result.ok();
}
3.4 Implementación frontal
3.4.1, interfaz API
Agregue api/wechat/menu.js
syncMenu() {
return request({
url: `${
api_name}/syncMenu`,
method: `get`
})
},
3.4.2 Agregar función de sincronización a la lista del menú
1. Botón Agregar
<el-button class="btn-add" size="mini" @click="syncMenu" >同步菜单</el-button>
2. Agregar método
syncMenu() {
this.$confirm('你确定上传菜单吗, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return menuApi.syncMenu()
}).then((response) => {
this.$message.success(response.message)
}).catch(error => {
console.log('error', error)
if (error === 'cancel') {
this.$message.info('取消上传')
}
})
}
4. Eliminar menú de inserción
4.1 Eliminar interfaz
4.1.1, interfaz de servicio
void removeMenu();
4.1.2 Implementación de la interfaz de servicio
@SneakyThrows
@Override
public void removeMenu() {
wxMpService.getMenuService().menuDelete();
}
4.1.3, interfaz del controlador
@PreAuthorize("hasAuthority('bnt.menu.removeMenu')")
@ApiOperation(value = "删除菜单")
@DeleteMapping("removeMenu")
public Result removeMenu() {
menuService.removeMenu();
return Result.ok();
}
4.2 Implementación frontal
4.2.1, interfaz API
Agregue api/wechat/menu.js
removeMenu() {
return request({
url: `${
api_name}/removeMenu`,
method: `delete`
})
}
4.2.2 Agregar función de sincronización a la lista del menú
1. Botón Agregar
<el-button class="btn-add" size="mini" @click="removeMenu">删除菜单</el-button>
2. Agregar método
removeMenu() {
menuApi.removeMenu().then(response => {
this.$message.success('菜单已删除')
})
}
3. Inicio de sesión autorizado de WeChat
La cuenta actual del empleado en segundo plano no está asociada con la cuenta de WeChat, por lo que al hacer clic en el menú de WeChat, es necesario determinar si iniciar sesión. Si es la primera visita, aparecerá la capa de asociación para establecer el vínculo entre la cuenta de WeChat y la cuenta del empleado.
1. Desarrollo de interfaz del lado del servidor
1.1 Configurar la penetración de la intranet (ngrok)
1.1.1 Usuarios registrados
URL: https://ngrok.cc/login/registrar
1.1.2 Autenticación de nombre real
(1) Después de un registro exitoso, inicie sesión en el sistema y realice la autenticación con el nombre real. La tarifa de autenticación es de 2 yuanes. El túnel solo se puede abrir después de pasar la autenticación.
1.1.3 Abrir el túnel
(1) Seleccione Gestión de túneles -> Abrir túnel
El último es un servidor gratuito, se recomienda elegir un servidor pago, 10 yuanes/mes, porque el servidor gratuito tiene muchos usuarios y a menudo cae.
(2) Haga clic en Comprar ahora -> Ingrese la información relevante
(3) Después de que la apertura sea exitosa, verifique el túnel abierto
Aquí se abren dos túneles, uno para la llamada de interfaz de backend y otro para la llamada de frontend de la cuenta oficial
1.1.4, iniciar el túnel
(1) Descarga la herramienta del cliente
(2) Seleccione la versión de Windows
(3) Descomprima, busque el archivo bat, haga doble clic para comenzar
(4) Ingrese la identificación del túnel, separe los múltiplos con comas y finalmente presione Entrar para comenzar
1.2 Configurar el "nombre de dominio de la página de devolución de llamada autorizada"
Tenga en cuenta que complete de acuerdo con su propia ID y nombre de dominio, ¡no lo use mal! ! ! ! !
Después de "[Autorización de la página web para obtener información básica del usuario]", haga clic en "Modificar" y agregue "Nombre de dominio de la página de devolución de llamada de autorización" (dirección de penetración de intranet de uso local)
1.3 Configure la devolución de llamada de autorización para obtener la dirección de la interfaz de información del usuario
wechat:
mpAppId: wx13db7dcf69bq1233
mpAppSecret: de3d7888d30febf84b64d041231e4027
# 授权回调获取用户信息接口地址
userInfoUrl: http://ggkt2.vipgz1.91tunnel.com/admin/wechat/userInfo
1,4, interfaz de controlador
package com.atguigu.wechat.controller;
import com.alibaba.fastjson.JSON;
import com.atguigu.common.jwt.JwtHelper;
import com.atguigu.common.result.Result;
import com.atguigu.model.system.SysUser;
import com.atguigu.system.service.SysUserService;
import com.atguigu.vo.wechat.BindPhoneVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.net.URLEncoder;
@Controller
@RequestMapping("/admin/wechat")
@Slf4j
public class WechatController {
@Resource
private SysUserService sysUserService;
@Autowired
private WxMpService wxMpService;
@Value("${wechat.userInfoUrl}")
private String userInfoUrl;
@GetMapping("/authorize")
public String authorize(@RequestParam("returnUrl") String returnUrl, HttpServletRequest request) {
//由于授权回调成功后,要返回原地址路径,原地址路径带“#”号,当前returnUrl获取带“#”的url获取不全,因此前端把“#”号替换为“guiguoa”了,这里要还原一下
String redirectURL = wxMpService.getOAuth2Service().buildAuthorizationUrl(userInfoUrl, WxConsts.OAuth2Scope.SNSAPI_USERINFO, URLEncoder.encode(returnUrl.replace("guiguoa", "#")));
log.info("【微信网页授权】获取code,redirectURL={}", redirectURL);
return "redirect:" + redirectURL;
}
@GetMapping("/userInfo")
public String userInfo(@RequestParam("code") String code,
@RequestParam("state") String returnUrl) throws Exception {
log.info("【微信网页授权】code={}", code);
log.info("【微信网页授权】state={}", returnUrl);
WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
String openId = accessToken.getOpenId();
log.info("【微信网页授权】openId={}", openId);
WxOAuth2UserInfo wxMpUser = wxMpService.getOAuth2Service().getUserInfo(accessToken, null);
log.info("【微信网页授权】wxMpUser={}", JSON.toJSONString(wxMpUser));
SysUser sysUser = sysUserService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getOpenId, openId));
String token = "";
//null != sysUser 说明已经绑定,反之为建立账号绑定,去页面建立账号绑定
if(null != sysUser) {
token = JwtHelper.createToken(sysUser.getId(), sysUser.getUsername());
}
if(returnUrl.indexOf("?") == -1) {
return "redirect:" + returnUrl + "?token=" + token + "&openId=" + openId;
} else {
return "redirect:" + returnUrl + "&token=" + token + "&openId=" + openId;
}
}
@ApiOperation(value = "微信账号绑定手机")
@PostMapping("bindPhone")
@ResponseBody
public Result bindPhone(@RequestBody BindPhoneVo bindPhoneVo) {
SysUser sysUser = sysUserService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhone, bindPhoneVo.getPhone()));
if(null != sysUser) {
sysUser.setOpenId(bindPhoneVo.getOpenId());
sysUserService.updateById(sysUser);
String token = JwtHelper.createToken(sysUser.getId(), sysUser.getUsername());
return Result.ok(token);
} else {
return Result.fail("手机号码不存在,绑定失败");
}
}
}
1.5, excluir interceptación
Configurar la intercepción de exclusión en la clase WebSecurityConfig
/**
* 配置哪些请求不拦截
* 排除swagger相关请求
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/admin/modeler/**","/diagram-viewer/**","/editor-app/**","/*.html",
"/admin/processImage/**",
"/admin/wechat/authorize","/admin/wechat/userInfo","/admin/wechat/bindPhone",
"/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
}
2. Procesamiento front-end móvil
2.1, agregar interfaz api
Agregar método en src/api/userInfo.js
bindPhone(bindPhoneVo) {
return request({
url: `/admin/wechat/bindPhone`,
method: 'post',
data: bindPhoneVo
})
},
2.2 Relación vinculante
Dado que es necesario acceder a todas las páginas del terminal móvil después de la autorización y el inicio de sesión, colocamos el negocio de procesamiento en la página de entrada para el procesamiento, scr/App.vue
<template>
<div id="app">
<router-view />
<el-dialog title="绑定手机" :visible.sync="dialogVisible" width="80%" >
<el-form ref="dataForm" :model="bindPhoneVo" size="small">
<h4>绑定你的手机号,建立云尚办公系统关联关系</h4>
<el-form-item label="手机号码">
<el-input v-model="bindPhoneVo.phone"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" icon="el-icon-check" @click="saveBind()" size="small">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import userInfoApi from '@/api/userInfo'
export default {
data() {
return {
show: true,
dialogVisible: false,
bindPhoneVo: {
openId: '',
phone: ''
}
};
},
created() {
// 处理微信授权登录
this.wechatLogin();
},
methods: {
wechatLogin() {
// 处理微信授权登录
let token = this.getQueryString('token') || '';
let openId = this.getQueryString('openId') || '';
// token === '' && openId != '' 只要这种情况,未绑定账号
if(token === '' && openId != '') {
// 绑定账号
this.bindPhoneVo.openId = openId
this.dialogVisible = true
} else {
// 如果绑定了,授权登录直接返回token
if(token !== '') {
window.localStorage.setItem('token', token);
}
token = window.localStorage.getItem('token') || '';
if (token == '') {
let url = window.location.href.replace('#', 'guiguoa')
window.location = 'http://oa.atguigu.cn/admin/wechat/authorize?returnUrl=' + url
}
}
},
saveBind() {
if(this.bindPhoneVo.phone.length != 11) {
alert('手机号码格式不正确')
return
}
userInfoApi.bindPhone(this.bindPhoneVo).then(response => {
window.localStorage.setItem('token', response.data);
this.dialogVisible = false
window.location = 'http://oa.atguigu.cn'
})
},
getQueryString (paramName) {
if(window.location.href.indexOf('?') == -1) return '';
let searchString = window.location.href.split('?')[1];
let i, val, params = searchString.split("&");
for (i=0;i<params.length;i++) {
val = params[i].split("=");
if (val[0] == paramName) {
return val[1];
}
}
return '';
}
}
};
</script>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
</style>
2.3 Agregar referencia de WeChat js
Agregue la referencia js en public/index.html
<script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js" type="text/javascript"></script>
2.4 Ajustar request.js
import axios from "axios";
// 创建axios实例
const service = axios.create({
baseURL: "http://oa.atguigu.cn", // api 的 base_url
timeout: 30000 // 请求超时时间
});
// http request 拦截器
service.interceptors.request.use(config => {
let token = window.localStorage.getItem("token") || "";
if (token != "") {
config.headers["token"] = token;
}
return config;
},
err => {
return Promise.reject(err);
});
// http response 拦截器
service.interceptors.response.use(response => {
if (response.data.code == 208) {
// debugger
// 替换# 后台获取不到#后面的参数
let url = window.location.href.replace('#', 'guiguoa')
window.location = 'http://oa.atguigu.cn/admin/wechat/authorize?returnUrl=' + url
} else {
if (response.data.code == 200) {
return response.data;
} else {
// 209没有权限 系统会自动跳转授权登录的,已在App.vue处理过,不需要提示
if (response.data.code != 209) {
alert(response.data.message || "error");
}
return Promise.reject(response);
}
}
},
error => {
return Promise.reject(error.response); // 返回接口返回的错误信息
});
export default service;
2.5 Prueba
4. Impulso de noticias
Para la información pendiente de aprobación, debemos notificar al aprobador para que apruebe la información. Después de que el aprobador haya aprobado la información, debemos notificar al solicitante que la envió para ver la información.
Usamos la interfaz de "mensaje de plantilla de WeChat" para enviar mensajes
1. Configurar el mensaje de plantilla de WeChat
1. Aprobación pendiente: { {first.DATA}} Número de aprobación: { {keyword1.DATA}} Hora de envío: { {keyword2.DATA}} { {content.DATA}}
2. Se ha procesado la aprobación: { {first.DATA}} Número de aprobación: { {keyword1.DATA}} Hora de envío: { { keyword2.DATA}} Aprobador actual: { {keyword3.DATA}} Estado de aprobación: { {keyword4.DATA}} { {content.DATA}}
2. Definir la interfaz
package com.atguigu.wechat.service;
public interface MessageService {
/**
* 推送待审批人员
* @param processId
* @param userId
* @param taskId
*/
void pushPendingMessage(Long processId, Long userId, String taskId);
/**
* 审批后推送提交审批人员
* @param processId
* @param userId
* @param status
*/
void pushProcessedMessage(Long processId, Long userId, Integer status);
}
3. Implementación de la interfaz de servicio
package com.atguigu.wechat.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.model.process.Process;
import com.atguigu.model.process.ProcessTemplate;
import com.atguigu.model.system.SysUser;
import com.atguigu.process.service.ProcessService;
import com.atguigu.process.service.ProcessTemplateService;
import com.atguigu.security.custom.LoginUserInfoHelper;
import com.atguigu.system.service.SysUserService;
import com.atguigu.wechat.service.MessageService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.Map;
@Slf4j
@Service
public class MessageServiceImpl implements MessageService {
@Resource
private WxMpService wxMpService;
@Resource
private ProcessService processService;
@Resource
private ProcessTemplateService processTemplateService;
@Resource
private SysUserService sysUserService;
@SneakyThrows
@Override
public void pushPendingMessage(Long processId, Long userId, String taskId) {
Process process = processService.getById(processId);
ProcessTemplate processTemplate = processTemplateService.getById(process.getProcessTemplateId());
SysUser sysUser = sysUserService.getById(userId);
SysUser submitSysUser = sysUserService.getById(process.getUserId());
String openid = sysUser.getOpenId();
//方便测试,给默认值(开发者本人的openId)
if(StringUtils.isEmpty(openid)) {
openid = "omwf25izKON9dktgoy0dogqvnGhk";
}
WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
.toUser(openid)//要推送的用户openid
.templateId("KvOVeW7jz4-DZgQ_WuXjMZO5I4pPA7L7fflVNwC_ZQg")//模板id
.url("http://oa.atguigu.cn/#/show/"+processId+"/"+taskId)//点击模板消息要访问的网址
.build();
JSONObject jsonObject = JSON.parseObject(process.getFormValues());
JSONObject formShowData = jsonObject.getJSONObject("formShowData");
StringBuffer content = new StringBuffer();
for (Map.Entry entry : formShowData.entrySet()) {
content.append(entry.getKey()).append(":").append(entry.getValue()).append("\n ");
}
templateMessage.addData(new WxMpTemplateData("first", submitSysUser.getName()+"提交了"+processTemplate.getName()+"审批申请,请注意查看。", "#272727"));
templateMessage.addData(new WxMpTemplateData("keyword1", process.getProcessCode(), "#272727"));
templateMessage.addData(new WxMpTemplateData("keyword2", new DateTime(process.getCreateTime()).toString("yyyy-MM-dd HH:mm:ss"), "#272727"));
templateMessage.addData(new WxMpTemplateData("content", content.toString(), "#272727"));
String msg = wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
log.info("推送消息返回:{}", msg);
}
@SneakyThrows
@Override
public void pushProcessedMessage(Long processId, Long userId, Integer status) {
Process process = processService.getById(processId);
ProcessTemplate processTemplate = processTemplateService.getById(process.getProcessTemplateId());
SysUser sysUser = sysUserService.getById(userId);
SysUser currentSysUser = sysUserService.getById(LoginUserInfoHelper.getUserId());
String openid = sysUser.getOpenId();
if(StringUtils.isEmpty(openid)) {
openid = "omwf25izKON9dktgoy0dogqvnGhk";
}
WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
.toUser(openid)//要推送的用户openid
.templateId("I0kVeto7T0WIDP6tyoHh-hx83wa9_pe7Nx9eT93-6sc")//模板id
.url("http://oa.atguigu.cn/#/show/"+processId+"/0")//点击模板消息要访问的网址
.build();
JSONObject jsonObject = JSON.parseObject(process.getFormValues());
JSONObject formShowData = jsonObject.getJSONObject("formShowData");
StringBuffer content = new StringBuffer();
for (Map.Entry entry : formShowData.entrySet()) {
content.append(entry.getKey()).append(":").append(entry.getValue()).append("\n ");
}
templateMessage.addData(new WxMpTemplateData("first", "你发起的"+processTemplate.getName()+"审批申请已经被处理了,请注意查看。", "#272727"));
templateMessage.addData(new WxMpTemplateData("keyword1", process.getProcessCode(), "#272727"));
templateMessage.addData(new WxMpTemplateData("keyword2", new DateTime(process.getCreateTime()).toString("yyyy-MM-dd HH:mm:ss"), "#272727"));
templateMessage.addData(new WxMpTemplateData("keyword3", currentSysUser.getName(), "#272727"));
templateMessage.addData(new WxMpTemplateData("keyword4", status == 1 ? "审批通过" : "审批拒绝", status == 1 ? "#009966" : "#FF0033"));
templateMessage.addData(new WxMpTemplateData("content", content.toString(), "#272727"));
String msg = wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
log.info("推送消息返回:{}", msg);
}
}
4. Pulsar llamada
4.1 Interfaz de instancia de proceso de inicio
segmento de código
@Autowired
private MessageService messageService;
@Transactional
public Process startUp(ProcessFormVo processFormVo) {
...
//计算下一个审批人
List<Task> taskList = this.getCurrentTaskList(processInstanceId);
if (!CollectionUtils.isEmpty(taskList)) {
List<String> assigneeList = new ArrayList<>();
for(Task task : taskList) {
SysUser sysUser = sysUserService.getByUsername(task.getAssignee());
assigneeList.add(sysUser.getName());
//推送消息给下一个审批人
messageService.pushPendingMessage(process.getId(), sysUser.getId(), task.getId());
}
process.setDescription("等待" + StringUtils.join(assigneeList.toArray(), ",") + "审批");
}
processMapper.updateById(process);
return process;
}
4.2 Interfaz de aprobación
segmento de código
public void approve(ApprovalVo approvalVo) {
...
//计算下一个审批人
Process process = this.getById(approvalVo.getProcessId());
List<Task> taskList = this.getCurrentTaskList(process.getProcessInstanceId());
if (!CollectionUtils.isEmpty(taskList)) {
List<String> assigneeList = new ArrayList<>();
for(Task task : taskList) {
SysUser sysUser = sysUserService.getByUsername(task.getAssignee());
assigneeList.add(sysUser.getName());
//推送消息给下一个审批人
messageService.pushPendingMessage(process.getId(), sysUser.getId(), task.getId());
}
process.setDescription("等待" + StringUtils.join(assigneeList.toArray(), ",") + "审批");
process.setStatus(1);
} else {
...
}
//推送消息给申请人
messageService.pushProcessedMessage(process.getId(), process.getUserId(), approvalVo.getStatus());
this.updateById(process);
}